- Common
- 经典算法
- 42.接雨水
- 54.螺旋矩阵
- 704.二分查找
- 415.字符串相加
- 69.x的平方根
- 4.寻找两个正序数字的中位数
- 8.字符串转换整数(atoi)
- 41.缺失的第一个正数
- 31.下一个排序
- 151.颠倒字符串中的单词
- 239.滑动窗口最大值
- 76.最小覆盖子串
- 20.有效的括号
- 15.三数之和
- 215.数组中的第K个最大元素
- 3.无重复字符的最长子串
- 155.最小栈
- 32.最长有效括号
- 43.字符串相乘
- 165.比较版本号
- 470.用Rand7()实现Rand10()
- 169.多数元素
- 34.在排序数组中查找元素的第一个和最后一个位置
- 14.最长公共前缀
- 48.旋转图像
- 128.最长连续序列
- 153.寻找旋转排序数组中的最小值
- 227.基本计算器2
- 468.验证IP地址
- 179.最大数
- 136. 只出现一次的数字
- 138. 复制带随机指针的链表
- 209. 长度最小的子数组
- 283.移动零
- LinkedList
- Recursion
- Greedy
- Dynamic Programming
- Depth First Search
- Breadth First Search
Common
经典算法
堆排序
构建大根堆(升序排序)或者小根堆(降序排序).
总体思路:
- 构建大根堆, 从最后一个非叶子节点(2*length-1)开始处理, (叶子节点没有子节点了, 所以从最后一个非叶子节点开始)
- 将第一个元素(最大元素)与最后一个元素交换
- 将前面元素再进行
调整, 保证还是一个大根堆 - 重复2.3步骤知道数组有序
调整算法思路:
- 先比较左右子树的大小, index指向最大的那个
然后比较index和根节点, 如果index>根节点, 那么交换index和根节点的位置, 否则退出循环, 已经是一个大根堆.保证这三个节点是一个大根堆, 然后再处理下面的元素(经过交换后, 根节点可能还比子节点小, 让根节点一直下沉),
i指向根节点(根节点经过交换后可能在左子树或者在右子树上)//Heap sortpublic static void heapSort(int[] nums){//首先构造大根堆, 从最后一个非叶子节点开始,length/2-1for (int i=nums.length/2-1;i>=0;i--){adjust(nums,i,nums.length);}//将大根堆的第一个元素(最大元素)于最后一个元素交换, 然后对前n-1个元素再进行调整for (int i=nums.length-1;i>0;i--){int temp=nums[i];nums[i]=nums[0];nums[0]=temp;adjust(nums,0, i);}}//Heap_sort's adjust functionpublic static void adjust(int[] nums,int i,int length){int temp=nums[i];//先用一个临时变量保存根节点for (int index=2*i+1;index<length;index=index*2+1){//index指向左子树if (index+1<length&&nums[index+1]>nums[index]){//比较左右节点的大小index++; //右子树更大, index*2+2, 指向右子树}if (nums[index]>t){//子树比父节点更大nums[i]=nums[index];i=index;}else {break;}}//最后进行赋值, 优化nums[i]=temp;}
42.接雨水

方法一:
先找到中间最高的柱子, 然后分别从两边向最高的柱子遍历. 最高柱子左边的柱子一定比它矮, 那么只记录左边柱子的最大值, 遍历到第i个柱子的时候, 与它左边最高的柱子比较, 如果比左边最高柱子矮, 那么表示可以接到水, 那么就用最高柱子-第i个柱子, 加到总数中, 否则那么第i个柱子就是左边最高的柱子. 同样的右边的遍历规则也是如此class Solution {public int trap(int[] height) {int max_height=0,index=0;int sum=0;//求出中间的最大高度for (int i=0;i<height.length;i++) {if (height[i]>max_height){max_height=height[i];index=i;}}//从左往右遍历int temp=0;for (int i=0;i<index;i++){if (height[i]>temp){temp=height[i];}else {sum+=temp-height[i];}}temp=0;for (int i=height.length-1;i>index;i--){if (height[i]>temp){temp=height[i];}else {sum+=temp-height[i];}}return sum;}}
方法二:
利用栈, 如下图所示




如果第i个柱子比栈顶的柱子高, 那么说明栈顶的柱子可能接到水, 将其出栈, 然后再观察栈顶的柱子,如果没有柱子了, 说明左边没有比第一次出栈的柱子高了, 那么这个柱子一定比第一次出栈的柱子高或者同样高, 那么算出栈顶柱子与第i个柱子的距离, 界限高度=两者谁的高度最小(木桶效应)-栈顶的柱子高度, 距离*界限高度 将其加入总和之中. 重复Stack<Integer> s = new Stack<>();int i=0,sum=0;while (i<height.length){//如果遍历到的柱子比栈顶的柱子高, 那么他们中间就可以储存水while(!s.isEmpty()&&height[i]>height[s.peek()]){int index_temp=s.pop();//将栈顶元素出栈//如果左边没有了元素,表示不能接水if(s.isEmpty()){break;}int left=s.peek();int distance=i-left-1;//sum+=distance*(Math.min(height[left],height[i])-height[index_temp]);}s.push(i++);}return sum;
54.螺旋矩阵

class Solution {public List<Integer> spiralOrder(int[][] matrix) {int row_length=matrix.length;int col_length=matrix[0].length;boolean[][] flag=new boolean[row_length][col_length];int direction=0;//方向标志int i=0,j=0;List<Integer> list=new ArrayList<>();while(!flag[i][j]){list.add(matrix[i][j]);flag[i][j]=true;switch (direction){case 0:if(j+1>=col_length||flag[i][j+1]==true){ //超过右边界, 或者下一个元素遍历过direction=(direction+1)%4;i++;}else{j++;}break;case 1:if(i+1>=row_length||flag[i+1][j]==true){direction=(direction+1)%4;j--;}else{i++;}break;case 2:if(j-1<0||flag[i][j-1]==true){direction=(direction+1)%4;i--;}else{j--;}break;case 3:if(i-1<0||flag[i-1][j]==true){direction=(direction+1)%4;j++;}else{i--;}break;}if(i<0||j<0||i>=row_length||j>=col_length||flag[i][j]==true){break;}}return list;}}
704.二分查找
class Solution {public int search(int[] nums, int target) {int left=0,right=nums.length-1;while(left<=right){int mid=left+(right-left)/2;if(nums[mid]==target){return mid;}else if(nums[mid]>target){right=mid-1;}else{left=mid+1;}}return -1;}}
415.字符串相加

class Solution {public String addStrings(String num1, String num2) {StringBuilder sb = new StringBuilder();int carry = 0, i = num1.length()-1, j = num2.length()-1;while(i >= 0 || j >= 0 || carry != 0){//这里i和j要判断是否大于等于0, 因为他们其中之一可能会小于0.if(i>=0) carry += num1.charAt(i--)-'0';if(j>=0) carry += num2.charAt(j--)-'0';sb.append(carry%10);carry /= 10;}return sb.reverse().toString();}}
69.x的平方根
利用二分法, 对于结果的整数部分k, 满足k2<=x, 那么我们就可以利用二分法来找结果, 如果mid2>x, 那么下界就等于mid, 反之同样的到里, 知道上界和下界的差值小于1, 算法结束, 因为一个数的平方根只会在两个相邻的整数之间
class Solution {public int mySqrt(int x) {if(x==1){return 1;}int right=x;int left=0;while(right-left>1){int mid=left+(right-left)/2;if(mid>x/mid){//注意这里right=mid;}else{//注意这里left=mid;}}return left;}}
4.寻找两个正序数字的中位数

就是找第k个大的数
主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
这里的 “/“ 表示整除
nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
这样 pivot 本身最大也只能是第 k-1 小的元素
如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums1 数组
如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 “删除”,剩下的作为新的 nums2 数组
* 由于我们 “删除” 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int length1 = nums1.length, length2 = nums2.length;int totalLength = length1 + length2;//如果是奇数, 那么求得的是一个数if (totalLength % 2 == 1) {int midIndex = totalLength / 2;//下标从0开始, 所以是要+1, 第1大, 第2大...的=数double median = getKthElement(nums1, nums2, midIndex + 1);return median;} else {int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;return median;}}//求第K大的数public int getKthElement(int[] nums1, int[] nums2, int k) {int length1 = nums1.length, length2 = nums2.length;int index1 = 0, index2 = 0;int kthElement = 0;while (true) {// 边界情况//如果nums1到头了, 说明nums2中的元素都比nums1大, 去nums2中寻找if (index1 == length1) {return nums2[index2 + k - 1];}if (index2 == length2) {return nums1[index1 + k - 1];}if (k == 1) {//求第1大的元素, 那么直接返回两个数组较小的首位元素return Math.min(nums1[index1], nums2[index2]);}// 正常情况int half = k / 2;int newIndex1 = Math.min(index1 + half, length1) - 1;int newIndex2 = Math.min(index2 + half, length2) - 1;int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];if (pivot1 <= pivot2) {//nums1前面的(newIndex1-index1+1)个元素不可能是第K大的元素, 所以k减去相应的数目.k -= (newIndex1 - index1 + 1);index1 = newIndex1 + 1;} else {k -= (newIndex2 - index2 + 1);index2 = newIndex2 + 1;}}}}
8.字符串转换整数(atoi)

class Solution {public int myAtoi(String s) {char[] arr=s.toLowerCase().toCharArray();int i=0;for(;i<arr.length;i++) {if(arr[i]!=' ') {break;}}boolean flag=false;if(i==arr.length||(arr[i]>='a'&&arr[i]<='z')||arr[i]=='.') {return 0;}if(arr[i]=='-') {flag=true;i++;}else if(arr[i]=='+'){flag=false;i++;}int sum=0;//sum=sum*10+digit>Integer.MAX_VALUE//sum>(Integer.MAX_VALUE-digit)/10,循环终止for(;i<arr.length&&arr[i]>='0'&&arr[i]<='9';i++) {if(sum>(Integer.MAX_VALUE-(arr[i]-'0'))/10) {if(flag) {return Integer.MIN_VALUE;}else {return Integer.MAX_VALUE;}}else {sum=sum*10+(arr[i]-'0');}}return flag? -sum:sum;}}
41.缺失的第一个正数


如果没有第二行的限制, 其实很简单, 但是有了限制, 先否定了排序,因为时间复杂度, 然后否定了哈希数组或者其他的数组, 所以只能利用原来的数组进行操作.
长度为N的数组, 没有出现的最小正整数只可能出现在[1,N+1]中, 因为[1,N]都出现了, 那么最小的正整数就是N+1, 如果[1,N]有没出现的, 那么最小的正整数就是在[1,N]中了.
遍历元素的算法如下:
- 先将数组中小于0的元素设为N+1的数
- 再将数组中<=N的数 对应下标-1(数组从0开始)的数置为负数, 表示遍历的数出现过, 如果此位置的数已经是负数, 那么不动它
从头遍历数组, 如果该数大于0, 那么表示不存在该下标, 那么就返回下标+1, 如果都小于0, 那么返回N+1
class Solution {public int firstMissingPositive(int[] nums) {int n = nums.length;for (int i = 0; i < n; ++i) {if (nums[i] <= 0) {nums[i] = n + 1;}}for (int i = 0; i < n; ++i) {int num = Math.abs(nums[i]);if (num <= n) {nums[num - 1] = -Math.abs(nums[num - 1]);}}for (int i = 0; i < n; ++i) {if (nums[i] > 0) {return i + 1;}}return n + 1;}}
31.下一个排序

下一个排列就是比当前排列大的最小排列组合.
为了达到目标, 将左边的较小数与左边的较大数交换, 此时的排列比原排列大, 还要满足, 较小数尽可能的靠右, 较大数尽可能的小, 只比较小数大, 这样才能保证变大地幅度最小, 也就是前面说地最小排列.
从后往前找较小值, 如果a[i]此时[a[i+1],a[n-1] ]为降序(因为 a[i]<a[i+1] ), 可以证明, 然后将其区间升序, 保证上升的幅度最小.class Solution {public void nextPermutation(int[] nums) {//先找最左的较小数, 从右往左找int left=0,right=nums.length-1,cur=nums.length-2;for (;cur>=0;cur--){if (nums[cur+1]>nums[cur]){left=cur;break;}}//如果存在, 否则说明数组为降序, 直接将其变为升序即可if (cur>=0){//在(left,n-1]中从后往前找到第一个比num[left]大的数, 即右边的较大数while(nums[right]<=nums[left]){right--;}//交换nums[left],nums[right]int temp=nums[left];nums[left]=nums[right];nums[right]=temp;//将(left,n-1]的数变为升序left++;right= nums.length-1;}while(left<right){int temp=nums[left];nums[left]=nums[right];nums[right]=temp;left++;right--;}}}
151.颠倒字符串中的单词

先去除字符串前后的空格,去除字符串中间的空格,将字符串整体翻装, 将单词反转class Solution {public String reverseWords(String s) {//去掉其中的多余空格s=s.trim();//正则表达式表示一个或者多个空格s=s.replaceAll("[\s]+"," ");char[] arr=s.toCharArray();//将整个字符串翻装revers(arr,0,arr.length-1);int cur=0;while(cur<arr.length){int k=cur;//将单词翻转while(k+1<arr.length&&arr[k+1]!=' '){k++;}revers(arr,cur,k);cur=k+2;}return new String(arr);}public void revers(char[] arr,int l,int r){while(l<r){char temp=arr[l];arr[l]=arr[r];arr[r]=temp;l++;r--;}}}
239.滑动窗口最大值

本来就是用的BF来解的, 每次算出窗口中的最大值, 但是超时了, 所以困难提题不能想的这么简单.
利用到的是单调队列, 即队列中的数是单调递增或者递减的, 那么队列头上的值就是最大的值
其中利用到的单调队列, 其实并没有维护窗口中的全部元素, 而是维护了可能成为最大值的元素
新元素入队时, 与队尾的元素比较, 如果比队尾的元素大, 那么将队尾的元素出队, 这个元素与新入队的元素相比, 不可能成为最大的元素, 直到队尾的元素比新元素大或者等于新元素, 将新元素插到队尾.
窗口滑动后元素出队时, 就与队首元素比较, 因为队列是个单调队列, 队首元素是最大的元素, 如果移除的元素是队首的元素, 那么就将队首的元素出队, 如果与队首元素不相等, 那么就不用管.
class Solution {public int[] maxSlidingWindow(int[] nums, int k) {int[] result=new int[nums.length-k+1];deque deque = new deque();for (int i=0;i<k;i++){ //先将前k个元素入队deque.push(nums[i]);}result[0]=deque.peek(); //求出前k个元素的最大值for (int i=1;i+k-1<nums.length;i++){ //处理后面的元素deque.poll(nums[i-1]); //将窗口移除的元素出队deque.push(nums[i+k-1]); //将窗口新增的元素入队result[i]=deque.peek(); //返回队列中最大的值}return result;}class deque{Deque<Integer> deque=new LinkedList<>();/*出队操作, 如果窗口移除的元素值等于队列头的值, 那么移除队列的值*/public void poll(int val){if (!deque.isEmpty()&&deque.peek()==val){deque.poll();}}/*入队操作, 如果窗口新加入的值大于队列入口的值, 那么将入口元素弹出, 直到入口的元素大于新元素, 将其入队*/void push(int val){while (!deque.isEmpty()&&deque.getLast()<val){deque.pollLast();}deque.addLast(val);}/*队列头的元素是最大的元素, 返回最大的元素*/int peek(){return deque.peek();}}}
76.最小覆盖子串


本题利用滑动窗口解决. left指向窗口的左边界, right指向窗口的右边界, 每次只移动一边.
- 首先利用一个哈希表map_t表示字符串t中的字符, 键为字符, 值为字符出现的个数.
- 用一个哈希表保存字符串s中的字符, 处理字符串s, 如果右指针指向的字符map_t中存在, 那么map_s中对应的元素个数加一, 这样一直移动右指针, 直到map_s中包含map_t中的全部元素, 且个数不能小于map_t中的个数, 此时右指针到左指针中包含了t字符串中的所有字符, 那么收缩左指针
收缩左指针, 如果left<=right, 且map_s包含了map_t中的所有元素, 那么left++, 向右移动, 此时还需要几个变量来保存最小的窗口, 以及最小的窗口时left和right的值, 与最小值比较. left向右移动, 直到map_s不包含map_t中的元素, 然后右回到了第2步, 继续向右寻找能全部包含字符串t中的窗口
class Solution {HashMap<Character, Integer> map_t = new HashMap<>(); //map_t来保存t的字符以及每个字符的个数HashMap<Character, Integer> map_s = new HashMap<>();public String minWindow(String s, String t) {//将t中de字符放入map_t中for (int i=0;i<t.length();i++){if (map_t.containsKey(t.charAt(i))){map_t.put(t.charAt(i),map_t.get(t.charAt(i))+1);}else{map_t.put(t.charAt(i),1);}}int left=0,right=-1,min_window=Integer.MAX_VALUE;//记录最小的子串长度int min_left=-1,min_right=-1; //记录最小字串的位置while (right<s.length()){right++;if (right<s.length()&&map_t.containsKey(s.charAt(right))){ //map_t中包含此字符map_s.put(s.charAt(right),map_s.getOrDefault(s.charAt(right),0)+1);//有就个数加一,没有就添加}while (check()&&left<=right){ //如果map_s中包含了map_t中所有的字符,个数也一样, 那么收缩左边界if (right-left+1<min_window){min_window=right-left+1;min_right=right;min_left=left;}if (map_t.containsKey(s.charAt(left))){ //如果map_t中包含左边界去除的元素,那么map_s中元素的值的对应减1map_s.put(s.charAt(left),map_s.getOrDefault(s.charAt(left),0)-1);}left++;}}return min_window==Integer.MAX_VALUE? "":s.substring(min_left,min_right+1);}public boolean check(){ //检查map_s中是否包含map_t中所有的字符Iterator<Map.Entry<Character, Integer>> iterator = map_t.entrySet().iterator();while(iterator.hasNext()){Map.Entry<Character,Integer> entry = iterator.next();Character key = entry.getKey();Integer value=entry.getValue();if (map_s.getOrDefault(key,0)<value){ //如果map_s的某一个字符的个数小于map_t对应字符的个数, 那么返回错误return false;}}return true;}}
下面的代码更加简介, 没有用到哈希表保存, 而是利用了数组, 将字符作为索引
public class Solution {public String minWindow(String s, String t) {char[] cs = s.toCharArray();char[] ct = t.toCharArray();int[] a = new int[128];//字符对应数组的下标for(int ch : ct){a[ch]--;}int left = 0,right = 0,len=0;String res = "";while (right < s.length()){//右边界指向的字符对应的下标数+1, 表示右边界字符存入数组中,对应的数+1a[cs[right]]++;//如果a[cs[right]]<=0, 说明t中包含此字符if(a[cs[right]] <= 0){len++;}while (len == t.length() && a[cs[left]] > 0){a[cs[left++]]--;}if(len == t.length()){if(res == "")res = s.substring(left,right+1);else if(res.length() > right-left+1){res = s.substring(left,right+1);}}right++;}return res;}}
20.有效的括号

class Solution {public boolean isValid(String s) {int n = s.length();if (n % 2 == 1) {return false;}Map<Character, Character> pairs = new HashMap<Character, Character>() {{put(')', '(');put(']', '[');put('}', '{');}};Deque<Character> stack = new LinkedList<Character>();for (int i = 0; i < n; i++) {char ch = s.charAt(i);if (pairs.containsKey(ch)) {if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {return false;}stack.pop();} else {stack.push(ch);}}return stack.isEmpty();}}
1.两数之和

利用哈希表降低复杂度, 将target-nums[i]放到哈希表中.class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();for (int i = 0; i < nums.length; ++i) {if (hashtable.containsKey(target - nums[i])) {return new int[]{hashtable.get(target - nums[i]), i};}hashtable.put(nums[i], i);}return new int[0];}}
15.三数之和

先将数组排序, 然后利用双指针, 遍历, 还有一个外循环控制从左往右找到所有的三元组.class Solution {public List<List<Integer>> threeSum(int[] nums) {//首先对数组进行排序,利用希尔排序for(int gap=nums.length/2;gap>0;gap/=2){for(int i=gap;i<nums.length;i++){int temp=nums[i];while(i-gap>=0&&nums[i-gap]>temp){nums[i]=nums[i-gap];i-=gap;}nums[i]=temp;}}List<List<Integer>> list1=new ArrayList<>();//利用双指针for(int i=0;i<nums.length-1;i++){ //下标从左往右for(int l=i+1,r=nums.length-1;l<r;){if(nums[i]+nums[l]+nums[r]==0){List<Integer> list2=new ArrayList<>();list2.add(nums[i]);list2.add(nums[l]);list2.add(nums[r]);list1.add(list2);l++;r--;}else if(nums[i]+nums[l]+nums[r]>0){r--;}else{l++;}}}return list1;}}
215.数组中的第K个最大元素

普通做法是, 利用快速排序, 将数组降序排好序, 返回下标为k-1的元素.
但是快速排序的实质是将一个元素归位, 对于降序排序, 它的左边元素都比它大, 它右边的元素都比它小, 此时它的下标已经确定了为i, 我们只是关心第K大的元素, 它左边或者右边的元素并不关心, 如果i大于K-1, 说明它不是第K大的元素, 而是第i+1大的元素, 比K小, 所以要在它的左边去找, 左边的元素都比它大, 所以放弃右边的元素, 这样能加速找到.
注意第9行加上了等于class Solution {int result=0;public int findKthLargest(int[] nums, int k) {fun(nums, 0, nums.length - 1, k);return result;}public void fun(int[] nums,int left,int right,int k){if (left<=right){int l=left,r=right;int temp=nums[l];//将大于nums[l]的元素和小于nums[l]的元素分别放到它的左右边, 利用快速排序的思想while (l<r){while(l<r&&nums[r]<=temp){ //降序排列r--;}nums[l]=nums[r];while (l<r&&nums[l]>=temp){l++;}nums[r]=nums[l];}nums[l]=temp;//如果nums[l]就是第k大的元素, 那么直接返回if (k-1==l){result=nums[l];return ;}else if(k-1>l){//如果第k大的元素在nums[l]的右边, 那么就到右边寻找fun(nums,l+1,right,k);}else {fun(nums,left,l-1,k);}}}}
3.无重复字符的最长子串

class Solution {public int lengthOfLongestSubstring(String s) {// 哈希集合,记录每个字符是否出现过Set<Character> occ = new HashSet<Character>();int n = s.length();// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动int rk = -1, ans = 0;for (int i = 0; i < n; ++i) {if (i != 0) {// 左指针向右移动一格,移除一个字符occ.remove(s.charAt(i - 1));}while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {// 不断地移动右指针occ.add(s.charAt(rk + 1));++rk;}// 第 i 到 rk 个字符是一个极长的无重复字符子串ans = Math.max(ans, rk - i + 1);}return ans;}}
155.最小栈

两种方法, 一种是使用辅助栈, 栈中保存最小的元素, 第二种是将最小值一起保存在一个栈中。//第一种方法class MinStack {Stack<Integer> min_stack=new Stack<>();Stack<Integer> stack=new Stack<>();public MinStack() {min_stack.push(Integer.MAX_VALUE);}public void push(int val) {stack.push(val);min_stack.push(Math.min(min_stack.peek(),val));}public void pop() {stack.pop();min_stack.pop();}public int top() {return stack.peek();}public int getMin() {return min_stack.peek();}}//第二张方法class MinStack {Stack<Integer> stack=new Stack<>();/** initialize your data structure here. */public MinStack() {}public void push(int val) {if(stack.isEmpty()){stack.push(val);stack.push(val);}else{int temp=stack.peek();stack.push(val);if(temp<val)stack.push(temp);elsestack.push(val);}}public void pop() {stack.pop();stack.pop();}public int top() {return stack.get(stack.size()-2);}public int getMin() {return stack.peek();}}
32.最长有效括号

先用栈处理一边字符串, 将其中的无效括号设为1, 有效括号设为0, 然后从中寻找最长连续0class Solution {public int longestValidParentheses(String s) {Stack<Integer> stack = new Stack<>();int[] flag = new int[s.length()];for (int i = 0; i < s.length(); i++) {//只将左括号入栈if (s.charAt(i) == '(') {stack.push(i);} else {//如果栈中为空, 那么此右括号无效if (stack.isEmpty()) {flag[i] = 1;//否则栈中只有左括号, 出栈一个左括号} else {stack.pop();}}}//如果栈中还有括号, 那么都为无效的括号while (!stack.isEmpty()) {flag[stack.pop()] = 1;}int max_length = 0;int length = 0;for (int i = 0; i < flag.length; i++) {if (flag[i] == 1) {length = 0;continue;}length++;max_length = Math.max(length, max_length);}return max_length;}
43.字符串相乘


class Solution {public String multiply(String num1, String num2) {if(num1.equals("0")||num2.equals("0")){return "0";}int length_1=num1.length();int length_2=num2.length();String ans="0";for (int i=length_1-1;i>=0;i--){StringBuilder sub_sb = new StringBuilder();int carry=0;//需要将后面补零, 也就是将前面补零.for(int j=length_1-1;j>i;j--){sub_sb.append(0);}int int_num1=num1.charAt(i)-'0';for (int j=length_2-1;j>=0;j--){int int_num2=num2.charAt(j)-'0';int sub_result=int_num1*int_num2+carry;sub_sb.append(sub_result%10);carry=sub_result/10;}if (carry!=0){sub_sb.append(carry);}ans=addString(ans,sub_sb.reverse().toString());}return ans;}//将相乘后的字符串相加public String addString(String num1,String num2){int i=num1.length()-1,j=num2.length()-1,caryy=0;StringBuilder sb = new StringBuilder();while (i>=0||j>=0||caryy!=0){int x= i>=0? num1.charAt(i)-'0':0;int y= j>=0? num2.charAt(j)-'0':0;int result=x+y+caryy;sb.append(result%10);caryy=result/10;i--;j--;}return sb.reverse().toString();}}
165.比较版本号

先分割, 然后利用Integer.valueOf方法将每一组转为整数比较, 注意读题, 每个修改号都可以转为32位整数class Solution {public int compareVersion(String version1, String version2) {String[] strings1 = version1.split("\\.");String[] strings2 = version2.split("\\.");int i=0;while (i<strings1.length&&i<strings2.length){int temp1=Integer.valueOf(strings1[i]);int temp2=Integer.valueOf(strings2[i]);if(temp1>temp2){return 1;}else if (temp1<temp2){return -1;}i++;}//string1已经遍历完成if(i==strings1.length){for (;i<strings2.length;i++) {if (Integer.valueOf(strings2[i])!=0){return -1;}}}else {for (;i<strings1.length;i++) {if (Integer.valueOf(strings1[i])!=0){return 1;}}}return 0;}}
470.用Rand7()实现Rand10()

class Solution extends SolBase {public int rand10() {//思路:使用ran7在[0,6]直接选一个数 使用ran7在[0,5]之间选一个数int a=rand7();int b=rand7();while(a==7) a=rand7();//a不能为7 必须为[0,6]这样才能保证奇偶都是1/2概率while(b>5) b=rand7();//b不能为5以上 因为一会可能要加5return ((a&1)==0? 0:5 )+b;//判断a奇偶性1/2 * b取值[0,5]之间1/5=1/10}}
169.多数元素

多数元素超过了n/2, 就从头开始遍历, 如果与target元素一样, 那么count+1, 否则count-1, 如果减到了0, 那么target等于下一个元素, 遍历完之后, target就指向多数元素.
这种过程可以抽象为 大乱斗, 有很多的国家参加, 遇到自己的人, 人数+1, 如果遇到不是自己的人, 那么人数就-1, 默认一换一, 那么厮杀过后, 剩下的肯定是多数元素.class Solution {public int majorityElement(int[] nums) {int target=nums[0];int count=1;for(int i=1;i<nums.length;i++){if(nums[i]==target){count++;}else{count--;if(count==0){target=nums[i+1];}}}return target;}}
34.在排序数组中查找元素的第一个和最后一个位置

class Solution {public int[] searchRange(int[] nums, int target) {int[] result=new int[2];result[0]=-1;result[1]=-1;// //基础解法,从头开始遍历// for(int i=0;i<nums.length;i++){// if(nums[i]==target){// if(result[0]==-1){// result[0]=i;// result[1]=i;// }else{// result[1]=i;// }// }// }// return result;/**进阶,时间复杂度为O(logn)考虑二分法解决,查找左右边界*///true表示左边界result[0]=fun(nums,target,true);result[1]=fun(nums,target,false);return result;}public int fun(int[] nums,int target,boolean flag){int res=-1;int l=0,r=nums.length-1;while(l<=r){int mid=l+(r-l)/2;if(target>nums[mid]){l=mid+1;}else if(target<nums[mid]){r=mid-1;}else{res=mid;if(flag==true){//如果找到目标数, 如果是左边界, 那么往左继续查找r=mid-1;}else{l=mid+1;}}}return res;}}
14.最长公共前缀

public static String longestCommonPrefix(String[] strs) {//初始值等于第一个String s=strs[0];for(String string:strs){//如果遍历的字符串不以初始值开始, 那么初始值就从末尾减少一个, 直至以初始值开头while(!string.startsWith(s)){s=s.substring(0,s.length()-1);}if (!string.startsWith(s))break;}return s;}
48.旋转图像

class Solution {public void rotate(int[][] matrix) {//先水平反转for (int i=0;i<matrix.length/2;i++){for (int j=0;j<matrix[0].length;j++){int temp=matrix[i][j];matrix[i][j]=matrix[matrix.length-i-1][j];matrix[matrix.length-i-1][j]=temp;}}//然后对角线反转for (int i=0;i<matrix.length;i++){for (int j=0;j<i;j++){int temp=matrix[i][j];matrix[i][j]=matrix[j][i];matrix[j][i]=temp;}}}}
128.最长连续序列

时间复杂度是O(n),使用Array.sort()肯定是不行的,其利用的是快速排序和归并排序,时间复杂度都是nlogn,不符合。
对于题目对时间复杂度有要求的,那么就用空间去换取时间,反之亦然。
这里使用HashSet来保存其中的数组,注意第三行先要将nums数组转换为包装类,再转为set
遍历到一个元素,那么就判断x,x+1,x+2……是否连续,这样复杂度还是O(n2),如果已知了x,x+1,x+2…x+y连续,当遍历到一个元素的时候,却重新从x+1,x+2…x+y开始匹配,得到的结果肯定没有从x开始枚举的结果好,所以当遇到一个元素m,如果m-1在set中不存在,则从m开始枚举,也就是说当m是连续数组中的第一个数时,才开始枚举。外层的复杂度为O(n),只有当一个数是连续序列中的第一个数时才进入内循环,所以每个数只会进入内存循环一次,因为如果一个数是某个连续序列中的数(不是首个元素),那么这个数跳过了。class Solution {public int longestConsecutive(int[] nums) {Set<Integer> set = Arrays.stream(nums).boxed().collect(Collectors.toSet());int max_length=0;for (Integer integer : set) {if (!set.contains(integer-1)){int temp=integer;int length=1;while (set.contains(temp+1)){temp++;length++;}max_length=Math.max(length,max_length);}}return max_length;}}
153.寻找旋转排序数组中的最小值

数组是旋转过的, 题目是要求O(logn)的时间复杂度, 那么就是二分法, 二分法对部分有序的数组同样有效的. 那么对于这道题, 要先找到无序的一半和有序的一半, 那么如何找到? 经过观察发现, 如果得到的中间元素比最右边的元素大, 那么说明右边时无序的, 左边是有序的, 这个可以很容易的发现, 那么找到了有序和无序的一半, 那么最小值在那一边, 一定是无序的那一边, 可以得到如下的算法, 注意第10行,right=mid, 返回的也是左边的元素class Solution {public int findMin(int[] nums) {int left=0,right=nums.length-1;while (left < right) {int mid=(left+right)/2;//表明中间的数比最右边的数大,那么右边是无序的,最小值在其中if (nums[mid]>nums[right]){left=mid+1;}else {//可能当前元素nums[mid]就是最小的元素,不能是right=mid-1right=mid;}}return nums[left];}}
227.基本计算器2

用一个栈保存当前的结果, 如果是+那么就将后面的数字直接入栈, 如果是-那么就将num取反压栈, 如果是*|/那么就取栈顶元素与num相乘或者相除后入栈, 利用一个preSign来保存先前的一个符号, 根据先前的符号来决定num的class Solution {public int calculate(String s) {char[] chars = s.toCharArray();Stack<Integer> stack=new Stack<>();int num=0;char preSign='+';for (int i=0;i<chars.length;i++){//如果是一个数字, 那么就加上if (Character.isDigit(chars[i])){num=num*10+chars[i]-'0';}//如果是符号,或者是最后一位, 将前面的数字根据先前的符号压栈if (!Character.isDigit(chars[i])&&chars[i]!=' '||i== chars.length-1){switch (preSign){case '+':stack.push(num);break;case '-':stack.push(-num);break;case '*'://如果先前的符号是'*', 那么就将本位与栈顶元素相乘后入栈stack.push(stack.pop()*num);break;case '/':stack.push(stack.pop()/num);break;}preSign=chars[i];num=0;}}int ans=0;while (!stack.isEmpty()){ans+=stack.pop();}return ans;}}
468.验证IP地址

class Solution {public String validIPAddress(String IP) {if (IP == null) {return "Neither";}String regex0 = "(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])";String regexIPv4 = regex0 + "(\\." + regex0 + "){3}";String regex1 = "([\\da-fA-F]{1,4})";String regexIPv6 = regex1 + "(:" + regex1 + "){7}";String result = "Neither";if (IP.matches(regexIPv4)) {result = "IPv4";} else if (IP.matches(regexIPv6)) {result = "IPv6";}return result;}}
"(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])"依次匹配1位,2位,3位的子段String regexIPv4 = regex0 + "(\\." + regex0 + "){3}"有三个., 所以要分开写179.最大数

自定义比较器, 比较s1+s2和s2+s1的大小class Solution {public String largestNumber(int[] nums) {int len_ = nums.length;String[] ss = new String[len_];for(int i=0; i<len_; i++){ss[i] = String.valueOf(nums[i]);}Arrays.sort(ss,(o1,o2)->(o2 + o1).compareTo(o1 + o2));StringBuilder sb = new StringBuilder();for(String s : ss) sb.append(s);return sb.charAt(0) == '0' ? "0" : sb.toString();}}//自己写的public String largestNumber(int[] nums) {Integer[] integers = Arrays.stream(nums).boxed().toArray(Integer[]::new);Arrays.sort(integers,(i, j)->{String s1=String.valueOf(i);String s2=String.valueOf(j);int sum1=Integer.valueOf(s1+s2);int sum2=Integer.valueOf(s2+s1);return sum2-sum1;});StringBuilder stringBuilder = new StringBuilder();for (Integer num : integers) {stringBuilder.append(num);}return stringBuilder.charAt(0)=='0'? "0":stringBuilder.toString();}
136. 只出现一次的数字


class Solution {public int singleNumber(int[] nums) {int result = 0;;for(int x : nums){result ^= x;}return result;}}
138. 复制带随机指针的链表

建立原节点和克隆节点的映射
public Node copyRandomList(Node head) {Node cur=head;HashMap<Node, Node> map = new HashMap<>();while (cur !=null){Node temp=new Node(cur.val);map.put(cur,temp);cur=cur.next;}cur=head;while (cur!=null){map.get(cur).next=map.get(cur.next);map.get(cur).random=map.get(cur.random);cur=cur.next;}return map.get(head);}
209. 长度最小的子数组

利用滑动窗口
public int minSubArrayLen(int target, int[] nums) {int i=0;int length=0;int sum=0;for (int j=0;j<nums.length;j++){sum+=nums[j];while (sum>=target){length= length==0? j-i+1:Math.min(length,j-i+1);sum-=nums[i++];}}return length;}}
283.移动零

设置一个index, 往后遍历, 不为0的设置在前面, 然后 将index之后的元素置零.
public void moveZeroes(int[] nums) {int index=0;for (int i = 0; i < nums.length; i++) {if (nums[i]!=0){nums[index++]=nums[i];}}for (;index<nums.length;index++){nums[index]=0;}}
LinkedList
142.环形联表Ⅱ


fast的速度是slow的两倍, 所以他们相遇时, fast走过的节点数是slow走过的两倍, 所以有一下等式
2(A+B)=A+2B+C
A=C
/*** Definition for singly-linked list.* class ListNode {* int val;* ListNode next;* ListNode(int x) {* val = x;* next = null;* }* }*/public class Solution {public ListNode detectCycle(ListNode head) {ListNode slow=head,fast=head;while(head!=null&&fast.next!=null&&fast.next.next!=null&&slow.next!=null){slow=slow.next;fast=fast.next.next;if(fast==slow){//如果快慢指针相遇, 那么就让慢指针指向头slow=head;//这里就是利用了上面得到的结论A=C, 让快慢指针速度一样, 那么就可以找到目标结点while(slow!=fast){slow=slow.next;fast=fast.next;}return slow;}}return null;}}
92.反转链表

可以看作是反转链表, 但是这题却是指定开始和结束的地方, 那么思路就是先找到开始的地方和结束的地方, 然后将开始到结束的地方完成翻转, 再将与前后的节点连接
//方法一class Solution {public ListNode reverseBetween(ListNode head, int left, int right) {ListNode shit=new ListNode(-1);shit.next=head;//首先切割链表ListNode cur=shit;ListNode pre=null;ListNode tail=null;ListNode start=null;//找到开始结点的前一个节点int i=0;for(;i<left-1;i++){cur=cur.next;}pre=cur;//反转子链表的开始节点start=cur.next;//找到right的下一个节点for(;i<right;i++){cur=cur.next;}tail=cur.next;//断开链表pre.next=null;cur.next=null;ListNode temp=fun(start);pre.next=temp;start.next=tail;return shit.next;}public ListNode fun(ListNode head){if(head==null){return null;}if(head.next==null){return head;}ListNode temp=fun(head.next);head.next.next=head;head.next=null;return temp;}}//方法二 头插法/*** 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 reverseBetween(ListNode head, int left, int right) {/**采用头插法*/ListNode dummyNode=new ListNode(-1);//临时的节点添加到头部, 避免复杂情况dummyNode.next=head;int i=0;ListNode cur=dummyNode;//找到left节点上一个节点while(i<left-1){cur=cur.next;i++;}//用一个节点先保存left的前一个节点ListNode temp=cur;cur=cur.next;i++;ListNode start=cur;ListNode nex=cur.next;ListNode pre=null;//退出循环的条件是cur指向right节点while(i<=right){cur.next=pre;pre=cur;cur=nex;nex=nex==null? null:nex.next;i++;}start.next=cur;temp.next=pre;return dummyNode.next;}}
23.合并K个升序链表

此题是链表数组, 数组中储存的都是链表的头节点. 开始的想法是利用N个指针, 指向头节点, 然后依次判断大小, 将小的头节点加入到新的节点当中去, 但是这样很复杂, 如果有X个链表, 那么每次的大小比较都要比较X个.
所以还是看了题解, 题解的思路是 利用优先队列, 相当于最小堆, 先将所有的头节点入队, 然后取出队列头的元素, 取出的元素就是队列中最小的, 将他加入新链表, 然后将他的后继节点加入队列. 这样循环下去, 直到队列中没有了元素.
public static ListNode fun(ListNode[] lists) {//创建优先队列, 利用优先队列将其中的值排列,PriorityQueue<ListNode> priorityQueue=new PriorityQueue<ListNode>(new Comparator<ListNode>() {@Overridepublic int compare(ListNode o1, ListNode o2) {return o1.val-o2.val;}});//将链表数组中的头节点入队for (ListNode list : lists) {if(list!=null) {priorityQueue.add(list);}}ListNode dummyList = new ListNode(-1);ListNode cur=dummyList;while (!priorityQueue.isEmpty()){ListNode temp=priorityQueue.poll();if(temp.next!=null){priorityQueue.add(temp.next);}cur.next=temp;cur=temp;}return dummyList.next;}
143.重排链表


虽然是中等题,但是考差了三个基本操作,非常好的一个题,考查了
- 利用快慢指针找到中间节点
- 反转链表
- 合并两个链表
本题的思路就是按照上面的步骤来解题的
class Solution {/*1. 先利用快慢指针找到中间的节点2. 再将后半段的链表反转3. 将前后链表合并*/public void reorderList(ListNode head) {if(head==null||head.next==null||head.next.next==null){return;}//找到中间的节点ListNode mid=mid(head);ListNode right=mid.next;mid.next=null;mid=head;//将右边的节点反转right=revers(right);//合并两个链表merge(mid,right);}public ListNode mid(ListNode head){ListNode fast=head;ListNode slow=head;//找到中间的节点, slow指向中间的节点while(fast!=null&&fast.next!=null){fast=fast.next.next;slow=slow.next;}return slow;}//采用头插法反转链表public ListNode revers(ListNode head){ListNode next=null;ListNode pre=null;ListNode cur=head;while(cur!=null){next=cur.next;cur.next=pre;pre=cur;cur=next;}return pre;}public void merge(ListNode left,ListNode right){ListNode left_next;ListNode right_next;while(left!=null&&right!=null){left_next=left.next;right_next=right.next;left.next=right;left=left_next;right.next=left;right=right_next;}}}
应该注意第42行,之前都是将其放在循环的最后,会导致一些问题
19.删除链表的倒数第N个结点

利用快慢指针, 先让快指针走n步, 然后快慢指针一起走, 知道快指针为空
class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {ListNode fast=head,slow=head;for(int i=0;i<n;i++){fast=fast.next;}//fast到了末尾, 说明删除的是头结点, 那么返回head.nextif(fast==null){return head.next;}while(fast!=null&&fast.next!=null){slow=slow.next;fast=fast.next;}slow.next=slow.next.next;return head;}}
82.删除排序列表中的重复元素

此题是删除重复的元素, 重复的元素全部删除, 而不是保存一个, 和一个题有点类似, 注意区分
next指向head的下一个节点,如果head的值和next值相等,那么next向后寻找,直到next的值和head的值不一样,此时head也要舍弃,则head=fun(next)
如果head的值和next值不相等,那么head.next=fun(next),处理下一个,head保留。
class Solution {public ListNode deleteDuplicates(ListNode head) {if(head==null||head.next==null){return head;}ListNode next=head.next;if(head.val==next.val){while(next!=null&&head.val==next.val){next=next.next;}head=deleteDuplicates(next);}else{head.next=deleteDuplicates(next);}return head;}}
148.排序链表


使用归并排序,先找到链表的中点,处理右边的列表,然后处理左边的链表,然后将两边合并起来返回
class Solution {public ListNode sortList(ListNode head) {return mergeSort(head);}public ListNode mergeSort(ListNode head){if(head==null||head.next==null){return head}//先利用快慢指针找到中间节点ListNode slow=head,fast=head.next.next;while(fast!=null&&fast.next!=null){fast=fast.next.next;slow=slow.next;}//slow指向中间节点//对右边进行归并ListNode r=mergeSort(slow.next);slow.next=null;ListNode l=mergeSort(head);return mergeNode(l,r);}public ListNode mergeNode(ListNode left,ListNode right){ListNode dummyNode=new ListNode(-1);ListNode cur=dummyNode;while(left!=null&&right!=null){if(left.val<right.val){cur.next=left;left=left.next;}else{cur.next=right;right=right.next;}cur=cur.next;}//处理剩下的节点cur.next= left==null? right:left;return dummyNode.next;}}
注意第10行是fast=fast.next.next, 而不是fast=head, 这样会造成错误, 当只有两个节点时,slow指向最后一个节点,slow.next为null,然后处理左边的时候就造成了死递归了,因为第一个节点的next没有断开。
2.两数相加

class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {int flag=0;//表示进位ListNode dummyNode=new ListNode(-1);ListNode cur=dummyNode;while(l1!=null||l2!=null||flag==1){int sum=0;if(l1!=null){sum+=l1.val;l1=l1.next;}if (l2!=null){sum+=l2.val;l2=l2.next;}if (flag==1){sum+=1;flag--;}flag=sum/10;sum=sum%10;cur.next=new ListNode(sum);cur=cur.next;}cur.next= l1!=null? l1:l2;return dummyNode.next;}}
22.链表中的倒数第k个节点

先让fast指针先走n(倒数第n个节点), 然后快慢指针一起走, fast指针到头时, slow指向目标节点
class Solution {public ListNode getKthFromEnd(ListNode head, int k) {ListNode fast=head;ListNode slow=head;int i=1;while(i<k){fast=fast.next;i++;}while(fast.next!=null){fast=fast.next;slow=slow.next;}return slow;}}
141.环形链表

利用快慢指针, 如果两个指针相遇那么就有环形回路
public class Solution {public boolean hasCycle(ListNode head) {ListNode slow=head,fast=head;while(fast!=null&&fast.next!=null){slow=slow.next;fast=fast.next.next;if(slow==fast){return true;}}return false;}}
25.Kge一组翻转链表

- 首先知道是否有K个链表, 如果没有, 那么就直接返回头结点, 此时check指向K+1个节点
- 如果有K个链表, 那么就采用头插法, 将列表翻转
递归处理第K+1个列表
class Solution {public ListNode reverseKGroup(ListNode head, int k) {ListNode pre=null;ListNode crr=head;ListNode next=null;ListNode check=head;int can=0;int count=0;while(can<k&&check!=null){ //检查是否有K个链表can++;check=check.next;}if(can==k){ //如果可以翻转, 采用头插法翻转链表while(count<k&&crr!=null){next=crr.next;crr.next=pre;pre=crr;crr=next;count++;}if(next!=null){//此时head指向末尾的结点head.next=reverseKGroup(check,k); //递归处理check+1个}return pre;}else{ //否则直接返回头结点return head;}}}
234.回文链表


先找到中间节点, 然后将后半部分的节点反装, 然后利用双指针判断是否为回文class Solution {public boolean isPalindrome(ListNode head) {if (head.next==null){return true;}//先找到中点节点ListNode slow=head,fast=head;//pre指向slow的上一个节点while (fast!=null&&fast.next!=null){fast=fast.next.next;slow=slow.next;}fast=revers(slow);slow=head;while (slow!=null&&fast!=null){if (slow.val!= fast.val){return false;}slow=slow.next;fast=fast.next;}return true;}public ListNode revers(ListNode head){if (head.next==null){return head;}ListNode temp=revers(head.next);head.next.next=head;head.next=null;return temp;}}
24.两两交换链表中的节点

class Solution {public ListNode swapPairs(ListNode head) {if(head==null||head.next==null){return head;}ListNode temp=head.next;head.next=swapPairs(head.next.next);temp.next=head;return temp;}}
Recursion
236.二叉树的最近公共祖先


/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val = x; }* }*/class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {//如果root为空那么直接返回null, 说明到头了if(root==null){return null;}//说明找到了了其中的一个结点if(root==p||root==q){return root;}//否则就去两边去查询TreeNode left=lowestCommonAncestor(root.left,p,q);TreeNode right=lowestCommonAncestor(root.right,p,q);//说明两边都找到了p或者q结点, 此时的root就是最近的祖先结点if(left!=null&&right!=null){return root;}else if(left!=null){//说明左边找到了p或者q, 右边没有找到p或者q, 说明left最先找到的return left;}else{return right;}}}
33.搜索旋转排序数


从mid处将两边分开, 那么一定有一边的数组是有序的, 如上图所示.
class Solution {public int search(int[] nums, int target) {return fun2(nums,0,nums.length-1,target);}public int fun2(int[] nums,int left,int right,int target){if(left>right){return -1;}int mid=left+(right-left)/2;//算出中间数if(nums[mid]==target){return mid;}//表示左半段是有序的if(nums[mid]>=nums[left]){if(nums[left]<=target&&target<nums[mid]){//查找左边return fun2(nums,left,mid-1,target);}else{//查找右边return fun2(nums,mid+1,right,target);}}else {//说明有半段是有序的if(nums[mid]<target&&target<=nums[right]){return fun2(nums,mid+1,right,target);}else{return fun2(nums,left,mid-1,target);}}}}
124.二叉树中最大路径和


class Solution {private int max_val=0;public int maxPathSum(TreeNode root) {fun(root);return max_val;}public int fun(TreeNode root){if (root==null){return 0;}int left=fun(root.left);int right=fun(root.right);int sum=left+right+root.val;int root_left=root.val+left;int root_right=root.val+right;//算出以根节点int temp_max_val=Math.max(Math.max(Math.max(root_left,root_right),sum),root.val);if(temp_max_val>max_val){max_val=temp_max_val;}//说明左节点加上根节点值 或者 右节点加上根节点的值 有大于0的, 可以返回其中的一个值if (root_left>0||root_right>0||root.val>0){//返回其中最大的值return Math.max(Math.max(root_left,root_right),root.val);}else {return 0;}}}
199.二叉树的右视图

自己的方法:
刚开始只想着只遍历右子树就行了, 但是发现左子树可能比右子树深, 从右侧也可以看到, 那么就用两个变量cur_level, right_max_depth,分别记录当前的深度和最大深度, 如果当前的深度大于最大深度, 就将此根节点加入列表中.
class Solution {private List<Integer> list=new ArrayList<>();private int cur_level=0,right_max_depth=0;//第一个记录当前的深度, 第二个记录右边最大的深度public List<Integer> rightSideView(TreeNode root) {fun(root);return list;}public void fun(TreeNode root){cur_level++;if(root!=null&&cur_level>right_max_depth){if(cur_level>right_max_depth){list.add(root.val);right_max_depth=Math.max(right_max_depth,cur_level);//获取右边最大的深度}}else{return ;}fun(root.right);//回溯, 减去右结点进入的深度cur_level--;fun(root.left);cur_level--;}}
然后发现每次都是先遍历右节点, 且每一层的结点只保存最右边的节点, 所以list中的size隐藏了最大深度, 这样就不用再用right_max_depth来保存最大深度了, 也不需要全局变量cur_level来保存当前的深度, 直接利用形参记录, 递归的深度就是当前的深度, 下面是看了评论的方法后总结的.
class Solution {private List<Integer> list=new ArrayList<>();public List<Integer> rightSideView(TreeNode root) {fun(root,0);return list;}public void fun(TreeNode root,int depth){if(root==null){return ;}if(depth==list.size()){list.add(root.val);}fun(root.right,depth+1);fun(root.left,depth+1);}}
105.从前序与中序遍历序列构造二叉树

首先要知道, 通过前序遍历和中序遍历可以构造唯一的二叉树.
class Solution {Map<Integer,Integer> map=new HashMap<>();public TreeNode buildTree(int[] preorder, int[] inorder) {//利用hashmap快一些for (int i=0;i<inorder.length;i++) {map.put(inorder[i],i);}return fun(preorder,inorder,0,preorder.length-1,0,inorder.length-1);}public TreeNode fun(int[] preorder,int[] inorder,int pre_left,int pre_right,int in_left,int in_right){if (pre_left>pre_right){return null;}int preRoot=pre_left; //前序序列的第一个元素即为头结点int inRoot = map.get(preorder[preRoot]); //返回的是前序遍历元素在中序遍历顺序中的下标int left_subtree_num=inRoot-in_left; //得出该元素左边还有多少个节点TreeNode root = new TreeNode(preorder[preRoot]);root.left=fun(preorder,inorder,pre_left+1,pre_left+left_subtree_num,in_left,inRoot-1); //递归处理root.right=fun(preorder,inorder,pre_left+left_subtree_num+1,pre_right,inRoot+1,in_right);return root;}}
21.合并两个有序链表

class Solution {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {//采用递归的方法if(l1==null){return l2;}else if(l2==null){return l1;}else if(l1.val<l2.val){l1.next=mergeTwoLists(l1.next,l2);return l1;}else{l2.next=mergeTwoLists(l1,l2.next);return l2;}}}
110.平衡二叉树

class Solution {public boolean isBalanced(TreeNode root) {return fun(root)!=-1;}public int fun(TreeNode root){if(root==null){return 0;}int left=fun(root.left),right=fun(root.right);if(left>=0&&right>=0&&Math.abs(left-right)<=1){ //高度没有超过1return Math.max(left,right)+1; //加上本节点的高度}else{return -1; //如果不平衡那么直接返回-1}}}
104.二叉数的最大深度

class Solution {public int maxDepth(TreeNode root) {if(root==null){return 0;}return Math.max(maxDepth(root.left),maxDepth(root.right))+1;}}
101.对称二叉树

是否为一个对称的二叉树, 不仅根节点要相等, 并且左树的左子树和右树的右子相等, 且左树的右子树和右树的左子树相等.
class Solution {public boolean isSymmetric(TreeNode root) {return check(root, root);}public boolean check(TreeNode p, TreeNode q) {if (p == null && q == null) {return true;}//如果其中有一个为null, 返回falseif (p == null || q == null) {return false;}//分别检查根节点的值是否相等,return p.val == q.val && check(p.left, q.right) && check(p.right, q.left);}}
543.二叉树的直径

class Solution {private int diameter=0;public int diameterOfBinaryTree(TreeNode root) {fun(root);return diameter;}public int fun(TreeNode root){if(root==null){return 0;}int left=fun(root.left);int right=fun(root.right);//直径是结点之间的边数, 所以这里不返回left+right+1diameter=Math.max(diameter,left+right);//这里返回左树或者右树直径最大的值 加上 根节点的值return Math.max(left,right)+1;}}
98.验证二叉搜索树

需要设置一个上下界, 如果超过上下界, 那么就不是搜索二叉树
对于一个根节点, 他左子树的上界就是根节点的值, 下界就是Long.MIN_VALUE, 对于左子树, 他的上界为Long.MAX_VALUE, 下界就是根节点的值, 这样递归处理
方法二是利用中序遍历, 观察二叉搜索树, 发现其中序遍历是升序, 先遍历左子树,然后是根节点, 最后是右子树.
class Solution {public boolean isValidBST(TreeNode root) {return fun(root,Long.MIN_VALUE,Long.MAX_VALUE);}public boolean fun(TreeNode root, long bottom, long top){if(root==null){return true;}if (root.val<=bottom||root.val>=top){return false;}//此时左子树的下界还是Long.MIN_VALUE, 上界变成了root.val, 左子树都比root.val小, 右子树同样的道理return fun(root.left,bottom,root.val)&&fun(root.right,root.val,top);}}方法二://利用中序遍历为升序class Solution {public boolean isValidBST(TreeNode root) {Deque<TreeNode> stack = new LinkedList<TreeNode>();double inorder = -Double.MAX_VALUE;while (!stack.isEmpty() || root != null) {//先将左子树全部放入栈中while (root != null) {stack.push(root);root = root.left;}root = stack.pop();// 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树if (root.val <= inorder) {return false;}inorder = root.val;//处理根结点的右子树root = root.right;}return true;}}
162.寻找峰值

看到O(logn)的就想到了二分查找,如果nums[mid]<nums[mid+1]那么右边一定有峰值,题目还保证了num[i]!=nums[i+1],那么mid+2元素要么比mid+1小,那么此时峰值元素就是mid+1;要么比mid+1大,那就继续向右查找,如果右边一直是升序,那么最后一个元素就是峰值,他比左边的元素大,比右边也大(越界,数值为负无穷)。同样左边也是一样的道理。
class Solution {public int findPeakElement(int[] nums) {return fun(nums, 0, nums.length - 1);}public int fun(int[] nums,int left,int right){if (left<=right){//算出中点int mid=(left+right)/2;//如果中点的元素比两边的大,那么直接返回if (nums[mid]>(mid-1>=0? nums[mid-1]:Integer.MIN_VALUE)&&numsb[mid]>(mid+1<= nums.length-1? nums[mid+1]:Integer.MIN_VALUE)){return mid;}else if (nums[mid]<(mid-1>=0? nums[mid-1]:Integer.MIN_VALUE)){//如果中间的元素比左边的小,那么左边一定有峰值return fun(nums,left,mid-1);}else {return fun(nums,mid+1,right);}}return 0;}}
240.搜索二维矩阵2


红色框中的元素都是大于中间元素的,绿色框中都是小于中间元素的
方法二是与方框中右上角的元素比较, 其元素比左边的元素都大, 比下边的元素都小, 那么将Target与其比较, 如果比其小, 那么target元素绝对不会在其元素的下边, 那么就将最右边的一列排除掉, 反之如果比其大, 那么target绝对不会在其元素的左边, 那么将最上方的元素排除.
class Solution {public boolean searchMatrix(int[][] matrix, int target) {return fun(matrix,0,matrix[0].length-1,0,matrix.length-1,target);}public boolean fun(int[][] matrix,int start_x,int end_x,int start_y,int end_y,int target){if (start_x>end_x||start_y>end_y){return false;}int mid_x=(start_x+end_x)/2;int mid_y=(start_y+end_y)/2;if (matrix[mid_y][mid_x]==target){return true;}else if (matrix[mid_y][mid_x]>target){//排除红色框中的元素return fun(matrix,start_x,mid_x-1,start_y,end_y,target)||fun(matrix,mid_x,end_x,start_y,mid_y-1,target);}else {//排除绿色框中的元素return fun(matrix,start_x,end_x,mid_y+1,end_y,target)||fun(matrix,mid_x+1,end_x,start_y,mid_y,target);}}}
394.字符串解码

class MySolution {public String decodeString(String s) {int num_start=0,num_end=0;StringBuilder sb = new StringBuilder();for (int i=0;i<s.length();i++){//这里将字符转为数字if (s.charAt(i)>='0'&&s.charAt(i)<='9'){num_start=i;num_end=i;while (s.charAt(num_end)>='0'&&s.charAt(num_end)<='9'){num_end++;}//此时num——end指向'[',//转换为数字int num=Integer.valueOf(s.substring(num_start,num_end));num_start=num_end+1;//来找与中括号匹配的右边int flag=0;while (num_end<s.length()){if (s.charAt(num_end)=='['){flag++;}if (s.charAt(num_end)==']'){flag--;}if (flag==0){//找到与第一个'['匹配的']'break;}num_end++;}i=num_end;String innerString=decodeString(s.substring(num_start,num_end));for (int j=0;j<num;j++){sb.append(innerString);}}else {//是普通字符,就加入sb中sb.append(s.charAt(i));}}return sb.toString();}}class Solution {public String decodeString(String s) {return fun1(s,0);}//shit表示']'结束的地方int shit=0;public String fun1(String s, int i) {if(i==s.length()) {return "";}String t="";if(Character.isLowerCase(s.charAt(i))) {t=s.charAt(i)+fun1(s,i+1);}else if(Character.isDigit(s.charAt(i))) {int j=0;//先算出数字while(Character.isDigit(s.charAt(i))) {j=j*10+(s.charAt(i)-'0');i++;}//此时i指向'['//然后再处理中括号中的字符串for(int x=0;x<j;x++) {t=t+fun1(s,i+1);}//s=number[String]String//表示加上中括号后的字符串,t=(number[String])+Stringt=t+fun1(s,shit+1);//如果遇到']'表示中间的字符串结束}else if(s.charAt(i)==']') {shit=i;}return t;}}
Greedy
300.最长递增子序列

目的是求最长的上升子序列, 最长表明每次上升的幅度最小, 那么用一个ints[len]来保存上升幅度最小的上升子序列,ints[len]=x, 表示数组中的前len个中最大的元素为x.
开始从左向右遍历数组, 先与ints数组最右边的元素, 也就是ints[len]作比较, 如果num[i]比其大,那么将其添加到ints数组的最后面, 否则利用二分查找去ints数组中查找比nums[i]小的那个元素, 找到后, 将nums[i]添加到找到的(假设ints[j])下一个元素(ints[j+1])中, 因为nums[i]比ints[j+1]相对于nums[i]上升的幅度更小, 更新ints数组.
class Solution {public int lengthOfLIS(int[] nums) {int[] ints = new int[nums.length + 1];//ints[i]表示以第i个结尾的最长上升子序列中最后一个数int len=1;ints[len]=nums[0];for(int i=1;i<nums.length;i++){if(nums[i]>ints[len]){//大于最后最长升序子序列中的最后一个, 那么就将其添加到末尾ints[++len]=nums[i];}else {int l=1,r=len,index=0;while (l<=r){int mid=(l+r)/2;if (ints[mid]<nums[i]){index=mid;l=mid+1;}else {r=mid-1;}}ints[index+1]=nums[i];}}return len;}}
112.路径总和

class Solution {public boolean hasPathSum(TreeNode root, int targetSum) {if(root==null)return false;if(root.right==null&&root.left==null)return targetSum-root.val==0;return hasPathSum(root.right,targetSum-root.val)||hasPathSum(root.left,targetSum-root.val);}}
122.买卖股票的最佳时间2

class Solution {public int maxProfit(int[] prices) {int sum_profit=0;int i=0;while(i<prices.length){int j=i+1;//如果当前的价格比买入的价格高, 那么往后while(j<prices.length&&prices[j]>=prices[j-1]){j++;}sum_profit+=prices[j-1]-prices[i];i=j;}return sum_profit;}}
Dynamic Programming
5.最长回文子串



public class Solution {public String longestPalindrome(String s) {int len = s.length();if (len < 2) {return s;}int maxLen = 1;int begin = 0;// dp[i][j] 表示 s[i..j] 是否是回文串boolean[][] dp = new boolean[len][len];// 初始化:所有长度为 1 的子串都是回文串for (int i = 0; i < len; i++) {dp[i][i] = true;}char[] charArray = s.toCharArray();// 递推开始// 先枚举子串长度for (int L = 2; L <= len; L++) {// 枚举左边界,左边界的上限设置可以宽松一些for (int i = 0; i < len; i++) {// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得int j = L + i - 1;// 如果右边界越界,就可以退出当前循环if (j >= len) {break;}if (charArray[i] != charArray[j]) {dp[i][j] = false;} else {//相邻两个字符只要相等, 那么就是回文串if (j - i < 3) {dp[i][j] = true;} else {dp[i][j] = dp[i + 1][j - 1];}}// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置if (dp[i][j] && j - i + 1 > maxLen) {maxLen = j - i + 1;begin = i;}}}return s.substring(begin, begin + maxLen);}}
300.最长递增子序列

利用一个一维数组动态规划, dp[i]=x表示以第i个数字(包括第i个)结尾的最长递增子序列长度为x, 此时已经计算了dp[0…i-1]的值, 状态转移方程为
表示dp[0…i-1]中最长上升子序列后面再加一个nums[i], 而dp[j]表示以第j个数字结尾的最长上升子序列, 如果能从dp[j]这个状态转移到dp[i]状态, 那么需要nums[i]>nums[j]. 整个数组中的最长上升子序列是dp[i]中的最大值
if (nums.length==0){return 0;}int[] dp = new int[nums.length];int maxlen=1;//这里做了初始化dp[0]=1;for(int i=1;i<nums.length;i++){//这里表示nums[i]自身就是一个长度为1的子序列,所以dp[i]就等于1dp[i]=1;for(int j=0;j<i;j++){//找到能从dp[0]...dp[i-1]转移到dp[i]的最大值if(nums[i]>nums[j]){dp[i]=Math.max(dp[j]+1,dp[i]);}}maxlen=Math.max(maxlen,dp[i]);}return maxlen;//第二种方法int[] ints = new int[nums.length + 1];//ints[i]表示以第i个结尾的最长上升子序列中最后一个数int len=1;ints[len]=nums[0];for(int i=1;i<nums.length;i++){if(nums[i]>ints[len]){//大于最后最长升序子序列中的最后一个, 那么就将其添加到末尾ints[++len]=nums[i];}else {//否则利用二分查找找到其合适的位置,下标从1开始int l=1,r=len,index=0;while (l<=r){int mid=(l+r)/2;if (ints[mid]<nums[i]){index=mid;l=mid+1;}else {r=mid-1;}}ints[index+1]=nums[i];}}return len;
注意第41行, 是直接将index+1的元素取代掉, 而不是向数组中添加元素, 因为题目要求的是子序列, 不能改变元素之间的相对位置, 且上升幅度最小.
70.爬楼梯

class Solution {public int climbStairs(int n) {int[] dp=new int[n+1];dp[1]=dp[0]=1;for(int i=2;i<=n;i++){dp[i]=dp[i-1]+dp[i-2];}return dp[n];}}
1143.最长公共子序列

class Solution {public int longestCommonSubsequence(String text1, String text2) {int[][] dp=new int[text1.length()+1][text2.length()+1]; //dp[i][j]表示text1[i]与text2[j]的最长公共子序列for(int i=0;i<text1.length();i++){for(int j=0;j<text2.length();j++){if(text1.charAt(i)!=text2.charAt(j)){ //不相等, 那么比较他们前面谁的大dp[i+1][j+1]=Math.max(dp[i+1][j],dp[i][j+1]);}else{dp[i+1][j+1]=dp[i][j]+1; //相等, 那么就是上一个状态加一得来的.}}}return dp[text1.length()][text2.length()];}}
121.买卖股票的最佳时机

利用一个一维数组来动态规划, dp[i]=x表示前i天的的最大利润, min记录前i天的最低价格, 看dp[i-1]大还是当天的价格price[i]-min大, 取最大的那个, 就是dp[i]的值
class Solution {public int maxProfit(int[] prices) {int[] dp=new int[prices.length]; //dp表示前i天最高的利润int min=prices[0];for(int i=0;i<prices.length;i++){//如果当前的价格比最低价格低, 那么最低价格就等于当天的价格if(prices[i]<min){min=prices[i];}dp[i]=Math.max(dp[i-1<0? 0:i-1],prices[i]-min);}return dp[prices.length-1];}}
53.最大子数组和


class Solution {public int maxSubArray(int[] nums) {int pre = 0, maxAns = nums[0];for (int x : nums) {pre = Math.max(pre + x, x);maxAns = Math.max(maxAns, pre);}return maxAns;}}
322.零钱兑换
//
先将dp中所有的元素设置为amount+1,表示不可能取该元素,dp[0]=0, 表示金额为0时,取0个硬币。
如果coins[i]小于等于i,i为金额,才有可能取该硬币。
class Solution {public int coinChange(int[] coins, int amount) {int[] dp=new int[amount+1];for(int i=1;i<dp.length;i++){dp[i]=amount+1;}//金额从1开始计算for(int i=1;i<=amount;i++){for(int j=0;j<coins.length;j++){//表示可以选取该硬币if(coins[j]<=i){dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);}}}return dp[amount]==amount+1? -1:dp[amount];}}
64.最小路径和

class Solution {public int minPathSum(int[][] grid) {int[][] dp=new int[grid.length][grid[0].length];dp[0][0]=grid[0][0];//处理上边界和左边界, 分别只能向右走和向下走for (int i=1;i<dp[0].length;i++){dp[0][i]=dp[0][i-1]+ grid[0][i];}for (int i=1;i<dp.length;i++){dp[i][0]=dp[i-1][0]+grid[i][0];}for (int i=1;i< grid.length;i++){for (int j=1;j<grid[0].length;j++){dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];}}return dp[dp.length-1][dp[0].length-1];}}
718.最长重复字数组

利用动态规划,dp[i][j]表示以i-1,j-1结尾的公共字符串的长度,两者相等的时候,dp[i][j] = dp[i-1][j-1] + 1
class Solution {public int findLength(int[] nums1, int[] nums2) {/*** 利用动态规划来*/if (nums1.length==0||nums2.length==0){return 0;}int[][] dp=new int[nums1.length+1][nums2.length+1];int result=0;for (int i=1;i<=nums1.length;i++){for (int j=1;j<=nums2.length;j++){//如果第i个和第j个相等,那么if (nums1[i-1]==nums2[j-1]){dp[i][j]=dp[i-1][j-1]+1;result=Math.max(result,dp[i][j]);}}}return result;}}
221.最大正方形


dp[i][j]表示以(i,j)为右下角,且只包含‘1’的最大正方形,算出所有的dp,那么其中的最大值就是我们所要求的值。现在考虑如何从其他的状态转为dp[i][j]。
如果[i][j]=‘0’,那么dp[i][j]=0,以此处为右下角的正方形不存在,因为此处就为0
如果[i][j]=‘1’,那么dp[i][j]的值由上方和左方以及左上方的最小值决定.
class Solution {public int maximalSquare(char[][] matrix) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return 0;}int max_length=0;int[][] dp=new int[matrix.length][matrix[0].length];for (int i=0;i<matrix.length;i++){for (int j=0;j<matrix[0].length;j++){if (matrix[i][j]=='1'){//表示在边界处,只能形成边长为1的正方形if (i == 0 || j == 0) {dp[i][j]=1;}else{dp[i][j]=Math.min(Math.min(dp[i-1][j-1],dp[i-1][j]),dp[i][j-1])+1;}max_length=Math.max(max_length,dp[i][j]);}}}return max_length*max_length;}}
62.不同路径

dp[i][j]表示走到第i行第j列的有多少个路径
如果i=0或者j=0,他们只能从上面或者左边走过来,此时路径只有一条
其他的情况就是可以从上边左边走过来,所以是上边和左边的路径个数之和。
public int uniquePaths(int m, int n) {int[][] dp=new int[m][n];for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(j==0||i==0){dp[i][j]=1;}else{dp[i][j]=dp[i-1][j]+dp[i][j-1];}}}return dp[m-1][n-1];}
还有一种解法是从起点到终点总共就需要m+n-2步,m步横着走,m-1步竖着走,那么就是C(m+n-2,m-1)=[(m+n-2)(m+n-3)…((m+n-2)- (m-1) +1)]/[(12…(m-1))]
public int uniquePaths(int m, int n) {long result=1;for(int i=n,y=1;y<m;i++,y++){result=result*i/y;}return (int)result;}
152.乘积最大子数组

这跟最大子序和很类似, 只不过是乘积, 根据前面的经验, 可以列出动态方程
f(x)表示以第i个元素结尾的最大乘积, 他可由上个状态转移过来, 或者自身较大. 但是在本题中是乘积, 会出现负负得正的情况, 如a={5,6,−3,4,−3}, 他的fmax(x)对应如下{5,30,−3,4,−3}, 按照之前的算法, 我们得到的答案为30, 但是实际答案是所有的乘积, 所以我们得到一个结论当前的最优解不一定是上一个状态的最优解得到的.
这里要分情况讨论, 分为正数和负数, 如果当前的位置是一个负数, 那么我们希望前面的最优值是是一个尽可能小的负数, 然后乘上本位, 负负得正, 得到一个尽可能大的正数. 如果本位是一个正数, 那么我们希望前面是一个尽可能大的正数, 乘上本位,就是尽可能大的正数. 得到如下的公式
最后遍历max_dp数组即可得到其中的最大值
class Solution {public int maxProduct(int[] nums) {int max=Integer.MIN_VALUE;int[] max_dp=new int[nums.length];int[] min_dp=new int[nums.length];max_dp[0]=nums[0];min_dp[0]=nums[0];for (int i=1;i<nums.length;i++){max_dp[i]=Math.max(Math.max(max_dp[i-1]*nums[i],min_dp[i-1]*nums[i]),nums[i]);min_dp[i]=Math.min(Math.min(max_dp[i-1]*nums[i],min_dp[i-1]*nums[i]),nums[i]);}int result=max_dp[0];for (int i : max_dp) {result=Math.max(result,i);}return result;}}
122.买卖股票的最佳时机2


class Solution {public int maxProfit(int[] prices) {int dp0=0; //表示第i天没有持有股票的最大利润int dp1=-prices[0];//表示第i天持有股票的最大利润for(int i=0;i<prices.length;i++){//第i天没有持有股票, 那么可能前一天也没有持有股票, 或者前一天持有股票但是在第i天卖出了dp0=Math.max(dp0,dp1+prices[i]);//第i天持有股票, 那么前一天股票, 或者前一天没有持有股票, 但是在i天买入股票了dp1=Math.max(dp1,dp0-prices[i]);}return dp0;}}
198.打家劫舍

如果只有一间房, 那么就最大金额就是偷这一家, 如果是两间房, 那么就偷金额较大的那个
如果数量大于2间, 那么就考虑第 i 间房子, 如果前一家被偷, 那么此间就不能偷了, 那么此时的最大金额就是dp[i-1], 如果第i-2间房子被偷了, 那么最大金额就是dp[i-2]+nums[i], 表示第i-2家被偷加上此间的金额.
public int rob(int[] nums) {if (nums==null||nums.length==0){return 0;}if(nums.length==1){return nums[0];}int[] dp = new int[nums.length];dp[0]=0;dp[1]=Math.max(dp[0],dp[1]);for (int i=2;i<nums.length;i++){dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);}return dp[dp.length-1];}
可以发现第i间只与第i-1和i-2间相关, 利用滚动数组来保存
public int rob(int[] nums) {if (nums==null||nums.length==0){return 0;}if(nums.length==1){return nums[0];}int first=nums[0];//表示第i-2家int second=Math.max(first,nums[1]);//表示第i-1家for (int i=2;i< nums.length;i++){int temp=second;second=Math.max(first+nums[i],second);first=temp;}return second;}
Depth First Search
200.岛屿数量


利用深度优先遍历整个二维字符数组, 如果是’1’, 那么以他为根进行深度遍历, 将遍历过的字符变为’0’, 直到没有地方可走.
此题也可以利用BFS广度优先遍历, 利用一个队列保存.
//深度优先class Solution {public int numIslands(char[][] grid) {int num=0;for(int i=0;i<grid.length;i++){for (int j=0;j<grid[0].length;j++){if (grid[i][j]=='1'){num++;fun(i,j,grid);}}}return num;}public void fun(int i,int j,char[][] grid){grid[i][j]='0';//上一个不越界, 且有陆地,才过去if (i-1>=0&&grid[i-1][j]=='1'){fun(i-1,j,grid);}if (i+1<grid.length&&grid[i+1][j]=='1'){fun(i+1,j,grid);}if (j-1>=0&&grid[i][j-1]=='1'){fun(i,j-1,grid);}if (j+1<grid[0].length&&grid[i][j+1]=='1'){fun(i,j+1,grid);}}}//广度优先class Solution {public int numIslands(char[][] grid) {int num=0;Queue<Integer> queue=new LinkedList<>();for(int i=0;i<grid.length;i++){for (int j=0;j<grid[0].length;j++){if (grid[i][j]=='1'){num++;grid[i][j]='0';//这里不是将元素入队, 而是将元素的index入队queue.offer(i*grid[0].length+j);//遍历以第一个为根节点遍历所有与他相连的陆地while(!queue.isEmpty()){Integer poll = queue.poll();int row=poll/grid[0].length;int clo=poll%grid[0].length;//上一个不越界, 且有陆地,才过去if (row-1>=0&&grid[row-1][clo]=='1'){queue.offer((row-1)*grid[0].length+clo);grid[row-1][clo] = '0';}if (row+1<grid.length&&grid[row+1][clo]=='1'){queue.offer((row+1)*grid[0].length+clo);grid[row+1][clo] = '0';}if (clo-1>=0&&grid[row][clo-1]=='1'){queue.offer(row*grid[0].length+clo-1);grid[row][clo-1] = '0';}if (clo+1<grid[0].length&&grid[row][clo+1]=='1'){queue.offer(row*grid[0].length+clo+1);grid[row][clo+1] = '0';}}}}}return num;}}
695.岛屿的最大面积

跟上面的题很相似,利用深度遍历或者广度遍历。
class Solution {private int area=0;public int maxAreaOfIsland(int[][] grid) {int max_area=0;for(int i=0;i<grid.length;i++){for(int j=0;j<grid[0].length;j++){//找到一块陆地,使用深度遍历来计算其最大面积if(grid[i][j]==1){fun(i,j,grid);}max_area=Math.max(max_area,area);area=0;}}return max_area;}public void fun(int i,int j,int[][] grid){grid[i][j]=0;area++;if(i-1>=0&&grid[i-1][j]==1){fun(i-1,j,grid);}if(j-1>=0&&grid[i][j-1]==1){fun(i,j-1,grid);}if(i+1<grid.length&&grid[i+1][j]==1){fun(i+1,j,grid);}if(j+1<grid[0].length&&grid[i][j+1]==1){fun(i,j+1,grid);}}}
46.全排列

二叉树是一个排列树, 利用了回溯法
class Solution {public List<List<Integer>> p_list=new ArrayList<>();public List<List<Integer>> permute(int[] nums) {fun(nums,0);return p_list;}/*排列树,利用回溯法*/public void fun(int[] nums,int i){if(i==nums.length-1){List<Integer> list=new ArrayList<>();for(int k:nums) {list.add(k);}p_list.add(list);return ;}for(int j=i;j<nums.length;j++){swap(i,j,nums);fun(nums,i+1);//将原来的两个数交换回去, 这里是一个回溯的过程swap(i,j,nums);}}public void swap(int i,int j,int[] nums){int temp=nums[i];nums[i]=nums[j];nums[j]=temp;}}
22.括号生成

利用DFS, 如果左括号剩余数量大于0, 那么就加上左括号, 如果右括号的剩余数量大于左括号的剩余数量, 那么加上一个右括号, 不必判断字符串最后一个字符是左括号还是右括号, 因为在生成的时候就是左括号先加入到字符串中, 只有右括号剩余数量大于左括号的时候再考虑加上右括号, 括号的是否有效已经隐藏了.
class Solution {private List<String> list=new ArrayList<>();public List<String> generateParenthesis(int n) {fun(n,n,"");return list;}public void fun(int left,int right,String str){if(left==0&&right==0){list.add(str);return;}if(left>0){fun(left-1,right,str+"(");}if (right>left){fun(left,right-1,str+")");}}}
93.复原IP地址

利用回溯法判断
class Solution {private List<String> result=new ArrayList<>();public List<String> restoreIpAddresses(String s) {backTrack(new StringBuilder(s),0,0);return result;}public void backTrack(StringBuilder s, int startIndex, int pointNum){if (pointNum == 3) {// 逗点数量为3时,分隔结束// 判断第四段⼦字符串是否合法,如果合法就放进result中if (isValid(s,startIndex,s.length()-1)) {result.add(s.toString());}return;}// ⬇这里要保证是三位数for (int i = startIndex;i<s.length() && i < startIndex+3; i++) {if (isValid(s, startIndex, i)) {s.insert(i+1,'.'); //在其后面插入一个'.'pointNum++;backTrack(s, i + 2, pointNum);// 插⼊逗点之后下⼀个⼦串的起始位置为i+2pointNum--;// 回溯s.deleteCharAt(i+1);//回溯的时候将其'.'删除} else {break;}}}//检查从start到end的字符串的数字是否合法public boolean isValid(StringBuilder s, int start, int end){if (start>=s.length()||s.charAt(start) == '0' && start != end||end-start>=3) { // 0开头的数字不合法return false;}int num = 0;for (int i = start; i <= end; i++) {num = num * 10 + (s.charAt(i) - '0');if (num > 255) { // 如果⼤于255了不合法return false;}}return true;}}
129.求根节点到叶子节点数字之和

class Solution {private int totalSum=0;public int sumNumbers(TreeNode root) {fun(root,0);return totalSum;}public void fun(TreeNode root, int sum) {sum = sum * 10 + root.val;if (root.left == null && root.right == null) { //到达叶子节点totalSum += sum;} else if (root.left != null && root.right != null) { //左右节点都需要遍历fun(root.left, sum);fun(root.right, sum);} else if (root.left == null) { //只需要遍历右节点fun(root.right, sum);} else { //只需要遍历左节点fun(root.left, sum);}}}
class Solution {public int sumNumbers(TreeNode root) {int sum=0;return fun(root,sum);}public int fun(TreeNode root,int sum){if(root==null){return 0;}//到达叶子结点,返回加上本结点的值if(root.left==null&&root.right==null){return sum*10+root.val;}return fun(root.left,sum*10+root.val)+fun(root.right,sum*10+root.val); //返回左右节点相加的结果}}
113.路径总和2

class Solution {List<List<Integer>> parent_list=new ArrayList<>();public List<List<Integer>> pathSum(TreeNode root, int targetSum) {fun(root,0,targetSum,new ArrayList<>());return parent_list;}public void fun(TreeNode root,int curSum,int targetSum,List list){if(root==null){return;}list.add(root.val);curSum+=root.val;if (root.left==null&&root.right==null&&curSum==targetSum){parent_list.add(new ArrayList<Integer>(list));}fun(root.left,curSum,targetSum,list);fun(root.right,curSum,targetSum,list);//回溯的过程,需要从list中移除本位, 是递归调用,list中的元素不会随着栈帧的出栈而将元素出栈,需要手动出栈, 但是curSum会自动减去list.remove(list.size()-1);}}
78.子集

class Solution {List<Integer> t = new ArrayList<Integer>();List<List<Integer>> ans = new ArrayList<List<Integer>>();public List<List<Integer>> subsets(int[] nums) {dfs(0, nums);return ans;}public void dfs(int cur, int[] nums) {if (cur == nums.length) {ans.add(new ArrayList<Integer>(t));return;}//选择当前的元素t.add(nums[cur]);dfs(cur + 1, nums);//将当前的元素移除, 表示不选择该元素t.remove(t.size() - 1);dfs(cur + 1, nums);}}
39.组合总和

使用回溯,注意第22行,start没有+1,因为元素可以重复使用。
class Solution {public List<List<Integer>> combinationSum(int[] candidates, int target) {List<List<Integer>> lists = new ArrayList<>();Arrays.sort(candidates);fun(candidates,target,lists,0,new ArrayList<Integer>());return lists;}public void fun(int[] candidates,int target,List<List<Integer>> res,int level,List<Integer> sub_list){if (target<0){return;}if (target==0){res.add(new ArrayList<>(sub_list));}for (int start=level;start< candidates.length;start++){if (target<0){break;}//将此元素现添加到其中sub_list.add(candidates[start]);//这里start不+1,因为此元素可以重复使用fun(candidates,target- candidates[start],res,start,sub_list);//再将此元素移除sub_list.remove(sub_list.size()-1);}}}
Breadth First Search
103.二叉树的锯齿形层序遍历

class Solution {public List<List<Integer>> zigzagLevelOrder(TreeNode root) {List<List<Integer>> List=new ArrayList<>();Queue<TreeNode> queue=new LinkedList<>();if(root!=null){ //先将root入列queue.offer(root);}else{return List;}int size=queue.size(); //先获取队列中的个数, 便于一层的遍历boolean flag=true; //flag来标志遍历顺序, 从左往右为true, 反之为falsewhile(!queue.isEmpty()){ //当队列不为空,循环List<Integer> subList=new ArrayList<>();while(size>0){TreeNode temp=queue.poll();//从左往右if(flag==true){subList.add(temp.val);}else{subList.add(0,temp.val);}if(temp.left!=null){queue.offer(temp.left);}if(temp.right!=null){queue.offer(temp.right);}size--;}List.add(subList);flag=!flag; //一层遍历完成, flag反转size=queue.size();}return List;}}
102.二叉树的层序遍历

class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> ret = new ArrayList<List<Integer>>();if (root == null) {return ret;}Queue<TreeNode> queue = new LinkedList<TreeNode>();queue.offer(root);while (!queue.isEmpty()) {List<Integer> level = new ArrayList<Integer>();int currentLevelSize = queue.size(); //用来记录本层的节点个数, 便于下面的遍历for (int i = 1; i <= currentLevelSize; ++i) { //将一层的结点添加至list中TreeNode node = queue.poll();level.add(node.val);if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}}ret.add(level);}return ret;}}
662.最大宽度


题目是求出最大的宽度, 首先想到的是BFS, 但是发现两个结点之间的空结点也算作宽度内. 这里利用到的是层序遍历结点的下标值, 同一层的结点的下标值之差就是他们之间的宽度.
那么每一个结点的下标都是由它的父节点下标的出来的, 设父节点的下标为position, 那么它的左节点下标为position2, 右节点下标为position2+1, 那么将一层的结点全部放入到list中, 用最后一个结点的下标减去第一个下标, 得到就是这层的宽度.
class Solution {public int widthOfBinaryTree(TreeNode root) {customNode node=new customNode(root,0);Queue<customNode> queue=new LinkedList<>();int max_width=0;//List<Integer> list=new ArrayList<>();queue.offer(node);while(!queue.isEmpty()){List<Integer> list=new ArrayList<>();int size=queue.size();for(int i=0;i<size;i++){customNode temp=queue.poll();list.add(temp.position);if(temp.treeNode.left!=null){queue.offer(new customNode(temp.treeNode.left,temp.position*2));}if(temp.treeNode.right!=null){queue.offer(new customNode(temp.treeNode.right,temp.position*2+1));}}max_width=Math.max(max_width,list.get(list.size()-1)-list.get(0)+1);}return max_width;}//自定义节点,保存节点以及他的下标值class customNode{TreeNode treeNode;int position;public customNode(TreeNode treeNode,int position){this.treeNode=treeNode;this.position=position;}}}
