1 两数之和
hashmap方法是目前最优解法。
思路:
一遍循环,存储到hashmap前查看是否有target-nums[i]
(返回一个结果可以提前break)
如果返回多个结果就用list存一下。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
HashMap<Integer,Integer> map = new HashMap<>();
for(int i =0;i<nums.length;i++){
if(map.containsKey(target-nums[i])){
res[0]=i;
res[1]=map.get(target-nums[i]);
}
map.put(nums[i],i);
}
return res;
}
}
2 两数相加
链表模拟,注意ListNode从头开始写也要会写。
这个题逆序链表比较简单。
l1l2只要有一个不为空就继续,对为空的val赋值0,然后求和结果+进位取余作为当前位。
循环结束要检查进位,如果不为0要部进位位。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = null, tail = null;
int carry = 0;
while (l1 != null || l2 != null) {
int n1 = l1 != null ? l1.val : 0;
int n2 = l2 != null ? l2.val : 0;
int sum = n1 + n2 + carry;
if (head == null) {
head = tail = new ListNode(sum % 10);
} else {
tail.next = new ListNode(sum % 10);
tail = tail.next;
}
carry = sum / 10;
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (carry > 0) {
tail.next = new ListNode(carry);
}
return head;
}
}
3 无重复字符的最长子串
其实也是一个模拟
用hashmap存一下比较好写,也可以用charflag[128]存一下。
思路就是两个指针left和right,保证两指针之间没有重复字符即可。
不断更新max
class Solution {
public int lengthOfLongestSubstring(String s) {
HashMap <Character,Integer> map = new HashMap<>();
int right=0,left=0;
int res= 0;
while(right<s.length()){
while(map.containsKey(s.charAt(right))){
map.remove(s.charAt(left));
left++;
}
map.put(s.charAt(right),right);
right++;
res = Math.max(res,right-left);
}
return res;
}
}
5 最长回文子串
暴力解了一下
这里暴力的思路比较清晰(实际上可以写的更简洁,但是没啥必要搞复杂直观比较重要)
遍历的是中心索引,就是索引为i的时候,有两种情况,一种是偶数长度,认为i和i+1应该相等,一种是奇数长度,认为i是中心,i-1和i+1相等。都求一下。
class Solution {
String res ="";
public String longestPalindrome(String s) {
if(s.length()==0)return res;
for(int i = 0; i<s.length();i++){
String tempRes1;
String tempRes2;
tempRes1 = getReverseLenth(s,i);
tempRes2 = getReverseLenth2(s,i);
if(tempRes1.length()>res.length())
res = tempRes1;
if(tempRes2.length()>res.length())
res = tempRes2;
}
return res;
}
public String getReverseLenth(String s, int i){
int left =i,right =i;
while(left>=0&&right<s.length()&&s.charAt(left)==s.charAt(right)){
left--;
right++;
}
return s.substring(left+1,right);
}
public String getReverseLenth2(String s, int i){
int left =i,right =i+1;
while(left>=0&&right<s.length()&&s.charAt(left)==s.charAt(right)){
left--;
right++;
}
return s.substring(left+1,right);
}
}
区间DP的思路,基本是一个完全的模版题.
dp[i][j]表示下标i到j是否是一个回文串。
初始化的话len=0都是true,j-i=0,表示单独一个字母是回文串。
len=1 i=j为true,表示两个相邻的相等为true;(这是上一种方法提到的两种情况各自的起始情况)
其余dp[i,j]=dp[i+1][j-1]&&ij字母相等
先对区间长度循环,然后对起点循环。
循环中处理len为0和len为1的特殊情况
len>=2之后都走dp状态转移方程即可。
每轮循环判断一下dp[i,j]如果为true,l+1是否比当前res.length大。
class Solution {
public String longestPalindrome(String s) {
boolean[][]dp = new boolean [s.length()][s.length()];
String res ="";
for( int l = 0;l<s.length();l++){
for(int i=0;i+l<s.length();i++){
int j = i+l;
if(l==0){
dp[i][j] = true;
}else if(l==1){
if(s.charAt(i)==s.charAt(j)){
dp[i][j]=true;
}
}else{
dp[i][j]=(s.charAt(i)==s.charAt(j))&&dp[i+1][j-1];
}
if(dp[i][j]==true && l>=res.length()){
res = s.substring(i,j+1);
}
}
}
return res;
}
}
6 Z字形变
使用stringBuilder 避免string重复建对象。
注意numRows=1的时候,getindex逻辑会出问题,单独拉出来。
(每一个字符归属于哪一行是固定的,也可以用index判断的方式直接分配)
首先创建一个StringBuffer数组,存储每一行的string
把每个字符扔进它应该去的行的末尾,通过getIndex函数获取行。
是一个完全的模拟过程。(这道题有数学方法,如果有必要后续扩展一下)
class Solution {
boolean flag= false;
public String convert(String s, int numRows) {
ArrayList<StringBuilder> resArray = new ArrayList<>();
for(int i =0;i<numRows;i++){
resArray.add(new StringBuilder());
}
int index= 0;
boolean flag =false;
//如果只有一行,不进循环,进了找index会报错,因为0 == numRows-1;
if(numRows==1)return s;
for(int i =0;i<s.length();i++){
resArray.get(index).append(s.charAt(i));
index = getNextIndex(index,numRows);
}
StringBuilder res = new StringBuilder();
for(int i = 0;i < resArray.size();i++){
res.append(resArray.get(i));
}
return res.toString();
}
public int getNextIndex(int curIndex, int numRows){
if(flag == false){
if(curIndex == numRows-1){
flag = true;
return curIndex-1;
}else{
return curIndex+1;
}
}
if(flag == true){
if(curIndex == 0){
flag = false;
return curIndex+1;
}else{
return curIndex-1;
}
}
return curIndex;
}
}
9 回文数
反转一半,注意末尾为0的时候单独处理
这个方法切记!小于0和以0结尾的数字要提前处理。但是x=0是可以进循环处理的。
方法就是用数学方法逐位计算后i位reverse的值,一直到x<=reverse。
如果是相等或者x==reverse/10说明偶数回文或者奇数回文。
其他情况x小于reverse切不是奇数回文到情况,就是不回文
class Solution {
public boolean isPalindrome(int x) {
int reverse = 0;
if(x<0||(x%10==0&&x!=0))return false;
while(x>reverse){
int num = x%10;
x=(x-num)/10;
reverse=reverse*10+num;
}
if(x==reverse||x==reverse/10)return true;
else return false;
}
}
11 盛水最多的容器
双指针,这个题可以当作一个方法记下来,相似的题目还有很多。
这是一个类似贪心到思路。
每次移动两个端点,只有移动较高的无论如何不能拿到更大的容积。(高固定,宽变小)
所以每次移动较矮的。相等时,移动哪一个都可以。(如果存在更大的容积,端点一定在二者之间而不是二者之一)
class Solution {
public int maxArea(int[] height) {
int res= 0;
int left=0,right = height.length-1;
while(left<right){
res = Math.max(res,(right-left)*Math.min(height[right],height[left]));
if(height[right]<height[left])
right--;
else
left++;
}
return res;
}
}
12 整数转罗马数字
查表。本来觉得需要一个num=0退出的判断,其实不需要,因为for循环中如果小于1直接就i+=出去了。
两个数组,由大到小。
遍历时依次比较,如果num包含一个value,那么就添加相应的字符,并且更新剩余num,如果小于,就向后移动。
更新时如果num被更新,i–。保证仍然对当前value比较,一直减到num为0,自然退出循环。
class Solution {
public String intToRoman(int num) {
int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
String[] symbols = {"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
StringBuilder res = new StringBuilder();
for(int i =0;i<values.length;i++){
if(num>=values[i]){
res.append(symbols[i]);
num=num-values[i];
i--;
}
}
return res.toString();
}
}
13 罗马数字转整数
弄个hashmap比较好查一些。(数量很少直接写函数switch case判断也可以)
charAt(i)究竟是加是减,取决与i+1处是大是小。
所以分情况,并且i=length-1时可以和正常的加情况合并。
class Solution {
public int romanToInt(String s) {
int[] values = {1000, 500, 100, 50, 10, 5,1};
char [] symbols = {'M','D','C','L','X','V','I'};
HashMap <Character,Integer> map = new HashMap<>();
int res = 0;
for(int i =0;i<symbols.length;i++){
map.put(symbols[i],values[i]);
}
for(int i =0;i<s.length();i++){
if(i+1<s.length()&&map.get(s.charAt(i+1))>map.get(s.charAt(i))){
res-=map.get(s.charAt(i));
}else{
res+=map.get(s.charAt(i));
}
}
return res;
}
}
14 最长公共前缀
纵向考虑。
可以分治,但是时间复杂度没有优化。
用一个res记录最后结果
对str[0]的i到len循环
比较其他str对应位置是否一致,
如果一致,该位添加到res,一旦出现不一致返回结果。
class Solution {
public String longestCommonPrefix(String[] strs) {
StringBuilder res = new StringBuilder();
if(strs.length == 0) return "";
int len = strs[0].length();
for(int i =0;i<len;i++){
char temp = strs[0].charAt(i);
for(int j=1;j<strs.length;j++){
if(strs[j].length()<=i||strs[j].charAt(i)!=temp){
return res.toString();
}
}
res.append(temp);
}
return res.toString();
}
}
15 三数之和
排序双指针
为了不重复,排序后遍历第一层循环意味着第一个数,然后双指针找其他两个数
双指针起点为i+1终点为len。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
ArrayList<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
for(int i=0;i<nums.length;i++){
int left = i+1;
int right = nums.length-1;
while(left<right){
if(nums[left]+nums[right]==-nums[i]){
ArrayList<Integer> tempRes = new ArrayList<>();
tempRes.add(nums[left]);
tempRes.add(nums[right]);
tempRes.add(nums[i]);
if(!res.contains(tempRes))
res.add(tempRes);
left++;
}else{
if(nums[left]+nums[right]<-nums[i]){
left++;
}else{
right--;
}
}
}
}
return res;
}
}
16 最接近的三数之和
基于15 简单修改了一下。
与15的区别在于不需要维护多个结果,只需要维护一个最接近的结果。
如果遇到==target直接返回即可。
class Solution {
public int threeSumClosest(int[] nums, int target) {
int res = nums[0]+nums[1]+nums[2];
Arrays.sort(nums);
for(int i=0;i<nums.length;i++){
int left = i+1;
int right = nums.length-1;
while(left<right){
if(nums[left]+nums[right]+nums[i] == target){
return target;
}else{
if(Math.abs(target-res)>Math.abs(target-(nums[left]+nums[right]+nums[i]))){
res = nums[left]+nums[right]+nums[i];
}
if(nums[left]+nums[right]+nums[i]<target){
left++;
}else{
right--;
}
}
}
}
return res;
}
}
17 电话号码的字母组合
DFS,用string恢复现场比较简单,可以优化为stringbuilder,注意一下添加和删除。
正常的DFS逻辑,搜索的时候每层变化的内容是当前是str和当前数位在digits中的index
每层循环遍历这个字符可能代表的字母。
出口为index或者temp长度等于digits的len
class Solution {
ArrayList<String> res = new ArrayList<>();
public List<String> letterCombinations(String digits) {
if(digits.length()==0)return res;
String [] table = new String[]{"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
search(digits,table,0, "");
return res;
}
public void search(String digits,String[]table,int index, String tempRes){
if(tempRes.length()==digits.length()){
res.add(tempRes);
return ;
}
for(int i = 0 ;i<table[digits.charAt(index)-'0'].length();i++){
String newRes = tempRes+table[digits.charAt(index)-'0'].charAt(i);
search(digits,table,index+1,newRes);
}
}
}
18 四数之和
排序+两层循环+双指针
循环中剪枝。
易错的点在于,不适用set和contains来去重。
思路:
排序
两层循环选定第一个数和第二个数(有序的),结果数无序的所以第二个数必须大于第一个数,否则结果会重复。
双指针找另外两个数,左指针也要大于第二个数。
注意的点在于,第一个数的选择要去重,选过1不能再选1,由于排序了,所以挨着。
第二个数也要去重,和第一个数一样。
双指针去重比较容易,只需要在命中target时移动时判断left++一直到不等的位置即可。
另外,剪枝最后考虑,如果最小的可能已经超过,break。(后面只会更大)
如果最大的可能已经小于,continue。(后面还可能继续变大)
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
if(nums.length<4) return res;
//先排序
Arrays.sort(nums);
for(int i =0;i<nums.length-3;i++){
//防止重复
if(i-1>=0&&nums[i]==nums[i-1])continue;
//两个剪枝条件
if(nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target)break;
if(nums[i]+nums[nums.length-1]+nums[nums.length-2]+nums[nums.length-3]<target)continue;
for(int j=i+1;j<nums.length-2;j++){
//防止重复
if(j>i+1&&nums[j]==nums[j-1])continue;
//两个剪枝条件
if(nums[i]+nums[j]+nums[j+1]+nums[j+2]>target)break;
if(nums[i]+nums[j]+nums[nums.length-2]+nums[nums.length-1]<target)continue;
//双指针
int left=j+1,right=nums.length-1;
while(left<right){
int temp =nums[i]+nums[j]+nums[right]+nums[left];
if(temp<target){
left++;
}else if(temp>target){
right--;
}else{
res.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
//防止重复
while(left+1<right&&nums[left+1]==nums[left])
left++;
left++;
}
}
}
}
return res;
}
}
19 删除链表的倒数第N个结点
快慢指针,一次遍历。
快指针先走n步,
慢指针和快指针一起走。
当快指针的下一个节点为空时
慢指针的下一个节点就是倒数第N个节点,删除即可
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next=head;
ListNode fast = dummy,slow=dummy;
while(n>0){
fast=fast.next;
n--;
}
while(fast.next!=null){
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return dummy.next;
}
}
20 有效的括号
如果括号再多几种就用hashmap存一下键值对比较容易写。
思路简单。
左括号进栈,右括号检查是否匹配。
如果s遍历完了,stack里面应该清空才是有效,否则无效。
class Solution {
public boolean isValid(String s) {
Stack<Character> stk = new Stack<>();
for(int i = 0;i<s.length();i++){
char tmp = s.charAt(i);
if(tmp=='('||tmp=='{'||tmp=='['){
stk.push(tmp);
}else{
if(stk.size()==0)return false;
char tmpPop = stk.pop();
if(!(tmpPop=='('&&tmp==')'||tmpPop=='{'&&tmp=='}'||tmpPop=='['&&tmp==']')){
return false;
}
}
}
if(stk.size()==0)return true;
return false;
}
}
21 合并两个有序链表
写来写去还是常规思路写的最舒服
合并就是这么写的,add有一些写法上的技巧避免写两个while。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode();
ListNode cur = dummy;
while(l1!=null&&l2!=null){
if(l1.val<=l2.val){
cur.next = l1;
l1=l1.next;
}else{
cur.next = l2;
l2=l2.next;
}
cur = cur.next;
}
if(l1==null){
cur.next=l2;
}else{
cur.next = l1;
}
return dummy.next;
}
}
22 括号生成
dfs
注意递归出口的判断
这里用了stringbuilder而不用string,可以避免频繁复制
用回溯+恢复现场的思路
思路就是记录左右括号的num
每层可以加做括号也可以加右括号。
在前面剪枝,left>n直接返回,left<right 直接返回。
如果temp的长度到了2n 添加到结果中即可。
StringBuilder 的这个方法用的比较少,记一下。deleteCharAt()
class Solution {
ArrayList<String> res = new ArrayList<>();
public List<String> generateParenthesis(int n) {
generater(n ,new StringBuilder(),0, 0);
return res;
}
public void generater(int n, StringBuilder tempStr, int leftNum, int rightNum){
if(leftNum>n) return ;
if(leftNum<rightNum) return ;
if(tempStr.length()==n*2){
res.add(tempStr.toString());
return ;
}
tempStr.append('(');
generater(n,tempStr,leftNum+1,rightNum);
tempStr.deleteCharAt(tempStr.length()-1);
tempStr.append(')');
generater(n,tempStr,leftNum,rightNum+1);
tempStr.deleteCharAt(tempStr.length()-1);
}
}
23 合并K个有序链表
基于合并两个有序链表做了一下
首先写一个合并两个有序链表。
然后基于这个用分治的方式合并K个。
K分奇偶情况,但是可以统一。
两两合并,唯一差异在于size为奇数,最后一轮没有i+1合并,直接保留下来。
合并之后,新的newlist[]作为参数递归调用。
如果newlistsize为1可以返回了。
用一个全局的res存储结果。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
ListNode res = new ListNode(0);
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length==0)return null;
if(lists.length==1){
res= lists[0];
return res;
}
int size = lists.length;
int newSize=0;
if(size%2==0) newSize=size/2;
else newSize=size/2+1;
ListNode [] newList = new ListNode[newSize];
for(int i =0;i<size;i+=2){
if(size%2==1&&i==size-1){
newList[i/2]=lists[i];
}else{
newList[i/2]=mergeTwoLists(lists[i],lists[i+1]);
}
}
mergeKLists(newList);
return res;
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode();
ListNode cur = dummy;
while(l1!=null&&l2!=null){
if(l1.val<=l2.val){
cur.next = l1;
l1=l1.next;
}else{
cur.next = l2;
l2=l2.next;
}
cur = cur.next;
}
if(l1==null){
cur.next=l2;
}else{
cur.next = l1;
}
return dummy.next;
}
}
24 两两交换链表中的节点
三个指针,做好交换交换和移动的顺序即可,最好画个图理解一下。
需要注意的是先找到next位置
然后cur.next.next先变化,然后cur.next,然后pre找位置,cur找位置。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next=head;
if(head == null)return null;
ListNode cur = dummy.next;
ListNode pre = dummy;
ListNode next;
while(cur!=null&&cur.next!=null){
next = cur.next.next;
pre.next=cur.next;
cur.next.next=cur;
cur.next=next;
pre=cur;
cur=pre.next;
}
return dummy.next;
}
}
25 K个一组翻转链表
首先依旧有一个dummy节点,叫hair是因为题解这么给的,有点意思。
首先K个一组翻转,可以想到需要两个指针,一个pre一个next,用于翻转后重新连接回来。
然后每组翻转的时候,就需要给一个head给一个tail,返回新的head和新的tail和外面的pre和next相连即可。
每轮循环先找到tail,这个过程可以判断是否够K个,不够直接返回即可。
找到tail之后,next也就找到了。
循环条件就是head不为空,继续往后走。
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode hair = new ListNode(0);
hair.next = head;
ListNode pre = hair;
while (head != null) {
ListNode tail = pre;
// 查看剩余部分长度是否大于等于 k
for (int i = 0; i < k; ++i) {
tail = tail.next;
if (tail == null) {
return hair.next;
}
}
ListNode nex = tail.next;
ListNode[] reverse = myReverse(head, tail);
head = reverse[0];
tail = reverse[1];
// 把子链表重新接回原链表
pre.next = head;
tail.next = nex;
pre = tail;
head = tail.next;
}
return hair.next;
}
public ListNode[] myReverse(ListNode head, ListNode tail) {
ListNode prev = tail.next;
ListNode p = head;
while (prev != tail) {
ListNode nex = p.next;
p.next = prev;
prev = p;
p = nex;
}
return new ListNode[]{tail, head};
}
}
26 删除排序数组中的重复项
原地。
原地的思路可以考虑双指针。
class Solution {
public int removeDuplicates(int[] nums) {
if(nums.length==0)return 0;
int i,j=1;
for(i=1,j=1;i<nums.length;i++){
if(nums[i]!=nums[i-1]){
nums[j]=nums[i];
j++;
}
}
return j;
}
}
27 移除元素
注意原地双指针即可
class Solution {
public int removeElement(int[] nums, int val) {
int i=0,j=0;
for(i=0,j=0;i<nums.length;i++){
if(nums[i]!=val){
nums[j]=nums[i];
j++;
}
}
return j;
}
}
28 strstr()
TODO 后续补充一下kmp
class Solution {
public int strStr(String haystack, String needle) {
int res= -1;
if(haystack.length()<needle.length())return res;
if(needle.equals("")) return 0;
for(int i =0;i<=haystack.length()-needle.length()&&i<haystack.length();i++){
int j =0;
int ii=i;
while(ii<haystack.length()&&j<needle.length()&&haystack.charAt(ii)==needle.charAt(j)){
if(j==needle.length()-1){
return i;
}
ii++;
j++;
}
}
return res;
}
}
29 两数相除
这里有两个需要积累的方法
一个是全部转换为负数防止溢出时的特殊判断
一个是减法时指数增加的思路(快速幂)
class Solution {
public int divide(int dividend, int divisor) {
int res = 0;
if(dividend==Integer.MIN_VALUE&&divisor==-1)return Integer.MAX_VALUE;
boolean flag = (dividend<0&&divisor<0||dividend>0&&divisor>0);
dividend =-Math.abs(dividend);
divisor = -Math.abs(divisor);
while(dividend<=divisor){
int temp = divisor;
int c=1;
while(dividend-temp<=temp){
temp=temp<<1;
c= c<<1;
}
res+=c;
dividend-=temp;
}
return flag==true?res:-res;
}
}
31 下一个排列
思路清晰之后再动笔会比较好,这次再调试过程中矫正思路花费时间较多
class Solution {
public void nextPermutation(int[] nums) {
//用于记录当前被交换的位置
int [] temp = new int[2];
//从最后一位开始考虑能否被交换
for(int k=nums.length-1;k>=0;k--){
temp[0]=nums[k];
temp[1]=k;
//从后向前依次考虑是否比被交换的数大,如果大就交换
for(int i =nums.length-1;i>k;i--){
if(nums[i]>temp[0]){
int tmpNum = nums[i];
nums[i]=temp[0];
nums[temp[1]]=tmpNum;
//交换之后数必变大,将交换位置之后的排序成为最小
int []newArray = Arrays.copyOfRange(nums,temp[1]+1,nums.length);
Arrays.sort(newArray);
//得到的结果就是比原数大,但是最小的数
for(int j=temp[1]+1;j<nums.length;j++){
nums[j]=newArray[j-temp[1]-1];
}
return ;
}
}
}
//如果没找到就排序返回
Arrays.sort(nums);
return ;
}
}
32 最长有效括号
暴力
class Solution {
int res = 0;
public int longestValidParentheses(String s) {
for(int i =0;i<s.length()-1;i++){
if(s.length()-i<res){
return res;
}
getLongest(s,i);
}
return res;
}
public void getLongest(String s,int index){
int left=0,right=0,count=0;
for(int i =index;i<s.length();i++){
if(s.charAt(i)=='('){
left++;
count++;
}else{
if(right==left){
return ;
}
right++;
count++;
}
if(left==right){
res = Math.max(count,res);
}
}
}
}
两侧贪心,双侧的目的是解决((()这类情况
class Solution {
public int longestValidParentheses(String s) {
int left = 0,right =0,res=0;
for(int i =0;i<s.length();i++){
if(s.charAt(i)=='(')left++;
if(s.charAt(i)==')')right++;
if(left==right)res = Math.max(res,left+right);
if(right>left){
left=0;
right=0;
}
}
left=0;
right=0;
for(int i =s.length()-1;i>=0;i--){
if(s.charAt(i)=='(')left++;
if(s.charAt(i)==')')right++;
if(left==right)res = Math.max(res,left+right);
if(right<left){
left=0;
right=0;
}
}
return res;
}
}
DP
状态转移方程比较难想
主要是当上一个字符为’)'时要向前追溯dp[i-1]长度
s.charAt(i - dp[i - 1] - 1) == '('保证了dp[i-1]是一个有效的区间 且dp[i]有前置左括号匹配.
所有不满足条件的默认dp[i]为0
class Solution {
public int longestValidParentheses(String s) {
int res = 0;
int [] dp = new int[s.length()];
for(int i =1;i<s.length();i++){
if(s.charAt(i)==')'){
if(s.charAt(i-1)=='('){
dp[i]=(i-2>=0?dp[i-2]:0)+2;
}else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
dp[i]=dp[i-1]+(i-dp[i-1]>=2?dp[i-dp[i-1]-2]:0)+2;
}
}
res = Math.max(res,dp[i]);
}
return res;
}
}
33 搜索旋转排序数组
二分法,好像和一个旋转数组很像.
二分法最后还是用了提前结束然后两个候选判断的方式
虽然麻烦一点,但是比较通用
class Solution {
public int search(int[] nums, int target) {
int finalMid = findMid(nums);
int left = 0, right = nums.length-1;
if(target==nums[right])return right;
//在前半段找
if(target<nums[right]){
left = finalMid;
while(left+1<right){
int mid = left+(right-left)/2;
if(target==nums[mid])return mid;
if(target>nums[mid])
left=mid;
else
right=mid;
}
if(target==nums[left])return left;
if(target==nums[right])return right;
//在后半段找
}else{
right = finalMid;
while(left+1<right){
int mid = left+(right-left)/2;
if(target==nums[mid])return mid;
if(target>nums[mid])
left=mid;
else
right=mid;
}
if(target==nums[left])return left;
if(target==nums[right])return right;
}
return -1;
}
//二分法找到右侧的起点
public int findMid(int[]nums){
int left =0,right = nums.length-1;
while(left+1<right){
int mid = left+(right-left)/2;
if(nums[mid]<nums[right])
right=mid;
else if(nums[mid]>nums[right])
left=mid;
else if(nums[mid] == nums[right])
right--;
}
if(nums[left]>nums[right]){
return right;
}else{
return left;
}
}
}
34 在排序数组中查找第一个和最后一个位置
注意特殊case的情况
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = 0,right = nums.length-1;
if(nums.length==0)return new int[]{-1,-1};
while(left+1<right){
int mid = left+(right-left)/2;
if(nums[mid]>=target)right =mid;
else{
left = mid;
}
}
int leftIndex=0,rightIndex=0;
if(nums[left]==target)leftIndex=left;
else if(nums[right]==target)leftIndex =right;
else leftIndex =-1;
left = 0;
right = nums.length-1;
while(left+1<right){
int mid =left+ (right-left)/2;
if(nums[mid]<=target)left=mid;
else{
right=mid;
}
}
//这里的顺序必须要调换一下,否则[2,2]会出问题
if(nums[right]==target)rightIndex=right;
else if(nums[left]==target)rightIndex=left;
else rightIndex=-1;
return new int []{leftIndex,rightIndex};
}
}
35 搜索插入位置
class Solution {
public int searchInsert(int[] nums, int target) {
for(int i = 0;i<nums.length;i++){
if(nums[i]>=target)return i;
}
return nums.length;
}
}
36 有效的数独
三个标志数组,注意一下frame数组的索引即可
class Solution {
public boolean isValidSudoku(char[][] board) {
boolean colFlag [][] = new boolean[9][9];
boolean rowFlag [][] = new boolean[9][9];
boolean frameFlag [][] = new boolean[9][9];
for(int i =0;i<9;i++){
for(int j=0;j<9;j++){
if(board[i][j]=='.')continue;
int num = board[i][j]-'1';
if(colFlag[j][num]==false)colFlag[j][num]=true;
else return false;
if(rowFlag[i][num]==false)rowFlag[i][num]=true;
else return false;
if(frameFlag[i/3*3+j/3][num]==false)frameFlag[i/3*3+j/3][num]=true;
else return false;
}
}
return true;
}
}
38 外观数列
可以考虑不需要数组
class Solution {
public String countAndSay(int n) {
String [] strArray = new String[n];
strArray[0]="1";
for(int i =1;i<n;i++){
StringBuilder tempStr = new StringBuilder();
char lastChar ='.';
int count=0;
for(int j=0;j<strArray[i-1].length();j++){
if(j==0){
count =1;
lastChar=strArray[i-1].charAt(j);
}else{
if(strArray[i-1].charAt(j)==lastChar){
count++;
}else{
tempStr.append(String.valueOf(count));
tempStr.append(lastChar);
count =1;
lastChar=strArray[i-1].charAt(j);
}
}
if(j==strArray[i-1].length()-1){
tempStr.append(String.valueOf(count));
tempStr.append(strArray[i-1].charAt(j));
}
}
strArray[i]=tempStr.toString();
}
return strArray[n-1];
}
}
不用数组(还是很慢)
class Solution {
public String countAndSay(int n) {
String strArray = "1";
for(int i =1;i<n;i++){
StringBuilder tempStr = new StringBuilder();
char lastChar ='.';
int count=0;
for(int j=0;j<strArray.length();j++){
if(j==0){
count =1;
lastChar=strArray.charAt(j);
}else{
if(strArray.charAt(j)==lastChar){
count++;
}else{
tempStr.append(String.valueOf(count));
tempStr.append(lastChar);
count =1;
lastChar=strArray.charAt(j);
}
}
if(j==strArray.length()-1){
tempStr.append(String.valueOf(count));
tempStr.append(strArray.charAt(j));
}
}
strArray=tempStr.toString();
}
return strArray;
}
}
39 组合总和
回溯+排序剪枝
class Solution {
List<List<Integer>>res= new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
//排序为了剪枝方便一些
Arrays.sort(candidates);
getCandi(candidates,target,0,new ArrayList<Integer>());
return res;
}
public void getCandi(int[] candidates,int target,int index, ArrayList<Integer>tempRes){
if(target==0){
res.add((ArrayList)tempRes.clone());
}
//加个剪枝
if(target<0||target<candidates[index])return ;
for(int i = index;i<candidates.length;i++){
tempRes.add(candidates[i]);
getCandi(candidates,target-candidates[i],i,tempRes);
tempRes.remove((Integer)candidates[i]);
}
}
}
40 组合总和II
同一个元素只能用一次
基于39 修改结果条件即可
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
//排序为了剪枝方便一些
Arrays.sort(candidates);
getCandi(candidates,target,0,new ArrayList<Integer>());
return res;
}
public void getCandi(int[] candidates,int target,int index, ArrayList<Integer>tempRes){
if(target==0){
res.add((ArrayList)tempRes.clone());
}
//加个剪枝
//修改点3 index+1可能越界,这里剪枝补充一个index判断
if(target<0||index>=candidates.length||target<candidates[index])return ;
int last = candidates[index]-1;
for(int i = index;i<candidates.length;i++){
//修改点1:去重,相同的元素不能两次出现在同一个位置(2 4和2 4是重复 但是22可以出现)
if(last==candidates[i])continue;
last = candidates[i];
tempRes.add(candidates[i]);
//修改点2:同一个元素使用一次后index后移
getCandi(candidates,target-candidates[i],i+1,tempRes);
tempRes.remove((Integer)candidates[i]);
}
}
}
41 缺失的第一个正数
归位
如果被移动位置已经是正确元素,或者位置非法,或目标位置已经是正确元素,什么都不做
否则交换。
class Solution {
public int firstMissingPositive(int[] nums) {
for(int i=0;i<nums.length;i++){
if(nums[i]==i+1||nums[i]>nums.length||nums[i]<=0||nums[nums[i]-1]==nums[i]){
}else{
int temp = nums[nums[i]-1] ;
nums[nums[i]-1]=nums[i];
nums[i]=temp;
i--;
}
}
for(int i =0;i<nums.length;i++){
if(nums[i]!=i+1) return i+1;
}
return nums.length+1;
}
}
42 接雨水
和盛水最多的容器场景差不多,但是思路不大一样。
最开始了考虑左右增加一个0高度
实际上在左右维护最高值的时候,可以直接添加进去
class Solution {
public int trap(int[] height) {
int [] left = new int[height.length];
int [] right = new int [height.length];
int res = 0;
int leftMax = 0,rightMax =0;
for(int i =0;i<height.length;i++){
left[i]=leftMax;
leftMax=Math.max(leftMax,height[i]);
}
for(int i =height.length-1;i>=0;i--){
right[i]=rightMax;
rightMax=Math.max(rightMax,height[i]);
}
for(int i=0;i<height.length;i++){
int num = Math.min(left[i],right[i])-height[i];
if(num>=0){
res+=num;
}
}
return res;
}
}
43 字符串相乘
这里使用了官方题解的方法2
设num1长度为m num2长度为n
m位数乘n位数可能等于m+n位,也可能等于m+n-1位。?
num1的个位索引为m-1,num2个位索引为n-1.
用res数组暂存结果。长度为m+n。
从右往左分别代表个位(十的0次方)、十位等等一直到 res[0]数位代表10的 m+n-1次方。
最低位相乘在个位,最高位相乘在m+n-1位,m+n位只能进位得到
最后判断一下进位即可。
class Solution {
public String multiply(String num1, String num2) {
int [] res = new int[num1.length()+num2.length()];
if (num1.equals("0") || num2.equals("0")) {
return "0";
}
for(int i=num2.length()-1;i>=0;i--){
int x = num2.charAt(i)-'0';
for(int j =num1.length()-1;j>=0;j--){
int y = num1.charAt(j)-'0';
res[i+j+1]+=x*y;
}
}
for(int i=res.length-1;i>0;i--){
res[i-1]+=res[i]/10;
res[i]=res[i]%10;
}
int index = (res[0]==0?1:0);
StringBuilder ans= new StringBuilder();
while(index<res.length){
ans.append(res[index]);
index++;
}
return ans.toString();
}
}
45 跳跃游戏II
DP
class Solution {
public int jump(int[] nums) {
if(nums.length==0)return 0;
int[]dp = new int[nums.length];
for(int i=1;i<nums.length;i++){
dp[i]=nums.length;
}
for(int i=0;i<nums.length;i++){
for(int j=i+1;j<nums.length&&j-i<=nums[i];j++){
dp[j]=Math.min(dp[i]+1,dp[j]);
}
}
return dp[nums.length-1];
}
}
贪心
class Solution {
public int jump(int[] nums) {
int rightIndex = 0;
int leftIndex=0;
int count = 0;
int newRightIndex= rightIndex;
while(rightIndex<nums.length-1){
while(leftIndex<=rightIndex){
newRightIndex=Math.max(leftIndex+nums[leftIndex],newRightIndex);
leftIndex++;
}
count++;
rightIndex=newRightIndex;
}
return count;
}
}
46 全排列
无重复数字全排列,随便操作
class Solution {
List<List<Integer>> res= new ArrayList<>();
boolean []flag;
public List<List<Integer>> permute(int[] nums) {
flag = new boolean[nums.length];
helper(nums,new ArrayList<>());
return res;
}
public void helper(int[]nums, ArrayList<Integer> tempRes){
if(tempRes.size()==nums.length)
res.add((ArrayList)tempRes.clone());
for(int i = 0;i<nums.length;i++){
if(flag[i]==true)continue;
else{
flag[i]=true;
tempRes.add(nums[i]);
helper(nums,tempRes);
tempRes.remove((Integer)nums[i]);
flag[i]=false;
}
}
}
}
47 全排列II
关于lastNum的判断优化了一下
不需要建一个int存储
用flag判断即可区分 是同层还是上下层
class Solution {
List<List<Integer>> res= new ArrayList<>();
boolean []flag;
public List<List<Integer>> permuteUnique(int[] nums) {
flag = new boolean[nums.length];
Arrays.sort(nums);
helper(nums,new ArrayList<>());
return res;
}
public void helper(int[]nums, ArrayList<Integer> tempRes){
if(tempRes.size()==nums.length)
res.add((ArrayList)tempRes.clone());
for(int i = 0;i<nums.length;i++){
if(flag[i]==true||(i>0&&nums[i]==nums[i-1]&&flag[i-1]==false))continue;
else{
flag[i]=true;
tempRes.add(nums[i]);
helper(nums,tempRes);
//这里remove用索引好于用元素
tempRes.remove(tempRes.size()-1);
flag[i]=false;
}
}
}
}
48 90度旋转图像
对角变换+轴对称
class Solution {
public void rotate(int[][] matrix) {
int m = matrix.length;
int n = matrix[0].length;
for(int i =0;i<m;i++){
for(int j = i+1;j<n;j++){
int temp = matrix[i][j];
matrix[i][j]=matrix[j][i];
matrix[j][i]=temp;
}
}
for(int i =0;i<m;i++){
for(int j = 0;j<n/2;j++){
int temp = matrix[i][j];
matrix[i][j]=matrix[i][n-j-1];
matrix[i][n-1-j]=temp;
}
}
return ;
}
}
49 字母异位词分组
排序的思路
用排好序的String作为hashmap的key
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
List<List<String>> res = new ArrayList<>();
HashMap<String,Integer> strToIndex = new HashMap<>();
for(int i =0;i<strs.length;i++){
char [] strArray = strs[i].toCharArray();
Arrays.sort(strArray);
String key = new String(strArray);
if(strToIndex.containsKey(key)){
res.get(strToIndex.get(key)).add(strs[i]);
}else{
List<String> temp = new ArrayList<>();
temp.add(strs[i]);
res.add(temp);
strToIndex.put(key,res.size()-1);
}
}
return res;
}
}
POW(X,N)
注意负数和越界
class Solution {
public double myPow(double x, int n) {
double res= 1;
boolean flag= false;
if(n==0)return 1;
if(n<0){
if(n==Integer.MIN_VALUE){
if(x==1)return 1;
if(x==-1)return 1;
return 0;
}
n=-n;
flag = true;
}
while(n>1){
if(n%2==0){
n=n/2;
x=x*x;
}else{
res*=x;
n=n-1;
}
}
res*=x;
if(flag==true)return 1/res;
return res;
}
}