leetcode 14天刷题计划-算法入门(共计31题)

文章目录

总结

双指针可以是左右指针,也可以是两个指针都在左边,也可以移动速度不一样
返回一个新创建的数组:return new int[]{i+1,j+1};
return String.valueOf(arr); //将字符数组转换为字符串

把list加进去result之后再修改list,仍然会改变result里面的内容

public static void main(String[] args) {
        Solution solution = new Solution();
        List<List<Integer>>result=new ArrayList();
        List<Integer>list=new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        result.add(list);
        System.out.println(result);
        list.remove(1);
        System.out.println(result);
       // System.out.println(solution.combine(4, 2));
    }

在这里插入图片描述

位运算技巧
n&(n-1)移除n的最低一位二进制为1的位

假设 nnn 的二进制表示为 (a10⋯0),其中 a表示若干个高位,1表示最低位的那个 1,0⋯00表示后面的若干个 0,那么 n−1 的二进制表示为:(a01⋯1)
在这里插入图片描述
我们我们知道2的n次方的前面的所有二进制位为1的时候加起来是2的n次方减1

由于负数是按照补码规则在计算机中存储的,−n的二进制表示为 n 的二进制表示的每一位取反再加上 1

计算机里的数是用二进制表示的,最左边的这一位一般用来表示这个数是正数还是负数,这样的话这个数就是有符号整数。如果最左边这一位不用来表示正负,而是和后面的连在一起表示整数,那么就不能区分这个数是正还是负,就只能是正数,这就是无符号整数

在这里插入图片描述

2021.08.03(第1 天)二分

二分查找细节详解,顺便赋诗一首

寻找一个数、寻找左侧边界、寻找右侧边界。而且,我们就是要深入细节,比如不等号是否应该带等号,mid 是否应该加一等等。分析这些细节的差异以及出现这些差异的原因,保证你能灵活准确地写出正确的二分查找算法。

寻找一个数时,用i<=j
寻找区间时,用i<j

1 704. 二分查找

class Solution {
    public int search(int[] nums, int target) {
          int i=0;int j=nums.length-1;
          while(i<=j){
              int mid=i+((j-i)>>1);
              if(nums[mid]>target) j=mid-1;
              else if(nums[mid]<target) i=mid+1;
              else return mid;
          }
          return -1;
    }
}

在这里插入图片描述

递归

class Solution {
    public int search(int[] nums, int target) {
          int i=0;int j=nums.length-1;
          return  dfs(nums, target, i,j);         
    }
    public int dfs(int[]nums,int target,int i,int j){
        if(i>j) return -1;
        int mid=i+((j-i)>>1);
        if(nums[mid]>target) return  dfs(nums, target, i,mid-1);
        else if (nums[mid]<target) return  dfs(nums, target, mid+1,j);
        else return mid;
    }
}

在这里插入图片描述

2 278. 第一个错误的版本

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int i=1,j=n;
        while(i<=j){      
        int mid =i+((j-i)>>1);
        if(!isBadVersion(mid))
        {
            if(mid+1<=n&&isBadVersion(mid+1))
             return mid+1;
            else
              i=mid+1;
        }
        else
        {
            if(mid-1>=0&&!isBadVersion(mid-1))
               return mid;
            else 
             j=mid-1;
        }
      }
      return 0;
    }
}

在这里插入图片描述

再看看官方。。。。

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1, right = n;
        while (left < right) { // 循环直至区间左右端点相同
            int mid = left + (right - left) / 2; // 防止计算时溢出
            if (isBadVersion(mid)) {
                right = mid; // 答案在区间 [left, mid] 中
            } else {
                left = mid + 1; // 答案在区间 [mid+1, right] 中
            }
        }
        // 此时有 left == right,区间缩为一个点,即为答案
        return left;
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/first-bad-version/solution/di-yi-ge-cuo-wu-de-ban-ben-by-leetcode-s-pf8h/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在这里插入图片描述

public class Solution extends VersionControl {
      public int firstBadVersion(int n) {
        int a=1;
       while(a<n){
           int s=a+(n-a)/2;
          if(isBadVersion(s)){
              n=s;
          }else{
              a=s+1;
          }
       }
       return a;
    }
}

3 35. 搜索插入位置

class Solution {
    public int searchInsert(int[] nums, int target) {
              int i=0;int j=nums.length-1;
             if(target<=nums[0]) return 0;
             if(target>nums[j]) return j+1;            
              while(i<j)
              {
                  int mid =i+((j-i)>>1);
                  if(nums[mid]>=target) 
                      j=mid;
                  else
                    i=mid+1;
              }
              return j;             
    }
}

在这里插入图片描述

2021.08.04(第2天) 双指针

4 977. 有序数组的平方

解题思路:头尾指针,计算头尾指针的平方值,将较大的存入arr[count],从数组的最后开始存,也可以从最前面开始存

本来想用O(1)空间,思路还是逆序存,但是当start的平方大于end的平方的时候不知道怎么处理

class Solution {
    public int[] sortedSquares(int[] nums) {
        int start=0,end=nums.length-1;
        int arr[]=new int[nums.length];
        int count=nums.length-1;
        while(start<=end){
            if(nums[start]*nums[start]<=nums[end]*nums[end]){        
                 arr[count--]=nums[end]*nums[end];
                 end--;
            }   else{
                  arr[count--]=nums[start]*nums[start];
                  start++;             
            }           
        }
        return arr;
    }
}

在这里插入图片描述
l++和r–可以合并到前面

class Solution {
    public int[] sortedSquares(int[] nums) {
        int l = 0;
        int r = nums.length - 1;
        int[] res = new int[nums.length];
        int j = nums.length - 1;
        while(l <= r){
            if(nums[l] * nums[l] > nums[r] * nums[r]){
                res[j--] = nums[l] * nums[l++];
            }else{
                res[j--] = nums[r] * nums[r--];
            }
        }
        return res;
    }
}


作者:carlsun-2
链接:https://leetcode-cn.com/problems/squares-of-a-sorted-array/solution/dai-ma-sui-xiang-lu-shu-zu-ti-mu-zong-ji-1rtz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

5 189. 旋转数组

解题思路,下标+k取模放到新的数组,然后把新数组复制回来

class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        int[] newArr = new int[n];
        for (int i = 0; i < n; ++i) {
            newArr[(i + k) % n] = nums[i];
        }
        System.arraycopy(newArr, 0, nums, 0, n);
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/rotate-array/solution/xuan-zhuan-shu-zu-by-leetcode-solution-nipk/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

思路2:
在这里插入图片描述

class Solution {
    public void rotate(int[] nums, int k) {
        k %= nums.length;
        reverse(nums, 0, nums.length - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.length - 1);
    }

    public void reverse(int[] nums, int start, int end) {
        while (start <= end) {
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start += 1;
            end -= 1;
        }
    }
}

2021.08.05 (第3天)双指针

6 283. 移动零

class Solution {
    public void moveZeroes(int[] nums) {
      int j=0;
      for(int i=0;i<nums.length;i++){
          if(nums[i]!=0){        
                nums[j]=nums[i];
                if(i>j) nums[i]=0;
                j++;        
          }       
      }       
    }
}

不加if(i>j)就是下面这样
在这里插入图片描述

官方这次搞复杂了啊,for循环变成了while我愣是没看懂

class Solution {
    public void moveZeroes(int[] nums) {
        int n = nums.length, left = 0, right = 0;
        while (right < n) {
            if (nums[right] != 0) {
                swap(nums, left, right);
                left++;
            }
            right++;
        }
    }

    public void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

7 167. 两数之和 II - 输入有序数组

解题思路:一个指针指向投,一个指针指向尾
如果头指针的值加上尾指针的值等于target直接返回这两个下标
如果头指针的值加上尾指针的值小于target,说明加数还不够,需要把头指针后移
如果头指针的值加上尾指针的值大于target,说明加数有多,需要把尾指针前移

class Solution {
    public int[] twoSum(int[] numbers, int target) {
       int i=0;int j=numbers.length-1;
       while(i<j){
           if(numbers[i]+numbers[j]<target) i++;
           else if(numbers[i]+numbers[j]>target) j--;
           else return new int[]{i+1,j+1};
       }
       return null;
    }
}

在这里插入图片描述

思路二:二分查找

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        for (int i = 0; i < numbers.length; ++i) {
            int low = i + 1, high = numbers.length - 1;
            while (low <= high) {
                int mid = (high - low) / 2 + low;
                if (numbers[mid] == target - numbers[i]) {
                    return new int[]{i + 1, mid + 1};
                } else if (numbers[mid] > target - numbers[i]) {
                    high = mid - 1;
                } else {
                    low = mid + 1;
                }
            }
        }
        return new int[]{-1, -1};
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/solution/liang-shu-zhi-he-ii-shu-ru-you-xu-shu-zu-by-leet-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021.08.06(第4天)双指针

8 344. 反转字符串

解题思路:每次交换左右指针的值,然后左指针后移,右指针前移。
注意,这个题用i<=j也是可以过的,因为每次都会移动两个指针

class Solution {
    public void reverseString(char[] s) {
       int i=0;int j=s.length-1;
       while(i<j){
           char temp=s[i];
           s[i++]=s[j];
           s[j--]=temp;       
       }
    }
}

在这里插入图片描述

9 557. 反转字符串中的单词 III

解题思路:字符串的底层char数组是final的,不能直接修改,所以,我们先把字符串转成一个可以修改的char数组,然后遍历这个数组,找到空格,就反转空格之前的所有字符,同时定义一个指针j记录当前已经反转到的位置,遍历到最后没有空格,就反转最后一个字符到倒数第一个空格之间的字符。
在这里插入图片描述

class Solution {
    public String reverseWords(String s) {
         //String arr[]=s.split(" "); 
         char arr[]=s.toCharArray(); 
         int j=0;             
         for(int i=0;i<arr.length;i++){
             if(arr[i]==' '){
                reverse(arr,j,i-1);
                j=i+1;
             }
             if(i==arr.length-1){//遍历到最后了
                  reverse(arr,j,i);
             }                                      
         }
         return String.valueOf(arr); //将字符数组转换为字符串
    }
    public void reverse(char[] s,int i,int j){
           while(i<j){
           char temp=s[i];
           s[i++]=s[j];
           s[j--]=temp;       
       }
    }
}

在这里插入图片描述

2021.08.07(第5天)双指针

10 876. 链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

解题思路:定义快慢指针,快指针每次走2步,慢指针每次走1步,快指针走到链表尾部的时候,慢指针恰好在链表中间

class Solution {
    public ListNode middleNode(ListNode head) {
          ListNode fast=head;
          ListNode slow=head;
          while(fast!=null&&fast.next!=null){
              fast=fast.next.next;
              slow=slow.next;
          }
          return slow;
    }
}

思路二:单指针法
对链表进行两次遍历。第一次遍历时,我们统计链表中的元素个数 N;第二次遍历时,我们遍历到第 N/2 个元素(链表的首节点为第 0 个元素)时,将该元素返回即可。

思路三:数组
考虑对链表进行遍历,同时将遍历到的元素依次放入数组 A 中。如果我们遍历到了 N 个元素,那么链表以及数组的长度也为 N,对应的中间节点即为 A[N/2]

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode[] A = new ListNode[100];
        int t = 0;
        while (head != null) {
            A[t++] = head;
            head = head.next;
        }
        return A[t / 2];
    }
}

11 19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?

解题思路:双指针
要删除倒数第n个节点,那么指针需要走总长度-n这么多步,
先让快指针走n步,然后快慢指针同时走,都走一步,快指针走length-n步到达终点,此时慢指针正好也走了length-n步

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //要删除倒数第n个节点,那么指针需要走总长度-n这么多步,
        //先让快指针走n步,然后快慢指针同时走,都走一步,快指针走length-n步到达重点,此时慢指针正好也走了length-n步
        ListNode fast=head;
        ListNode slow=head;
        if(head.next==null) return null;
        while(n>0&&fast!=null){
            fast=fast.next;
            n--;
        }
        if(fast==null)
            return head.next;
        while(fast.next!=null){
            fast=fast.next;
            slow=slow.next;
        }
        if(slow.next!=null){
            slow.next=slow.next.next;
        }  
        return head;     
    }
}

思路二,还是双指针,用一个假的头结点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head);
        ListNode first = head;
        ListNode second = dummy;
        for (int i = 0; i < n; ++i) {
            first = first.next;
        }
        while (first != null) {
            first = first.next;
            second = second.next;
        }
        second.next = second.next.next;
        ListNode ans = dummy.next;
        return ans;
    }
}

居然可以用递归
这个思路简直yyds,先递到最后一个节点,返回0,表示是倒数第0个节点,最后一个节点是倒数第一个节点,直到反向归回第一个节点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
    	int traverse = traverse(head, n);//traverse为链表的长度,比如链表长度为5那么表示第一个节点是倒数第5个节点,如果n正好等于链表长度,那么即去掉头结点,返回第二个节点
    	if(traverse == n)
    	    return head.next;
    	return head;
    }
    
    private int traverse(ListNode node, int n) {
        if(node == null)
            return 0;
        int num = traverse(node.next, n);
        if(num == n)
            node.next = node.next.next;
        return num + 1;
    }
}

比如1 2 3 4 5 ,要删除倒数第3个节点,就是下面这种情况

int traverse(2, 2) {
        if(2 == null)
            return 0;
       3 = traverse(3, 2);//表示4是倒数第2 个节点
        if(2 == 2)
            3.next = 3.next.next;
        return 3 + 1;//表示2是倒数第4个节点
    }

思路三:栈
遍历,把结点依次入栈,然后弹出n个节点,找到当前栈的头结点,

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head);
        Deque<ListNode> stack = new LinkedList<ListNode>();
        ListNode cur = dummy;
        while (cur != null) {
            stack.push(cur);
            cur = cur.next;
        }
        for (int i = 0; i < n; ++i) {
            stack.pop();
        }
        ListNode prev = stack.peek();
        prev.next = prev.next.next;
        ListNode ans = dummy.next;
        return ans;
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/solution/shan-chu-lian-biao-de-dao-shu-di-nge-jie-dian-b-61/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021.08.08(第6天)滑动窗口

12 3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

思路1:从左到右遍历字符,,如果没有重复字符,就把它加入HashMap,不用set是因为需要记录重复字符出现的位置,然后从重复字符的下一个字符开始继续遍历

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character,Integer>map=new HashMap();
        if(s.length()==0) return 0;
        int left=0;int max=1;
        for(int i=0;i<s.length();i++){
            if(!map.containsKey(s.charAt(i))){
               map.put(s.charAt(i),i);
               if(i==s.length()-1)//考虑最后一个字符重复的情况
                  max=Math.max(max,i-left+1);
            }              
            else{             
                if(map.get(s.charAt(i))<left){//考虑abba这种
                   map.put(s.charAt(i),i);
                   if(i==s.length()-1)//考虑最后一个字符重复的情况
                     max=Math.max(max,i-left+1);
                }                 
                else {
                max=Math.max(max,i-left);
                left=map.get(s.charAt(i))+1;              
                i--;
                }              
            }           
        }
        return max;
    }
}

在这里插入图片描述

13 567. 字符串的排列

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,s1 的排列之一是 s2 的 子串 。

思路:1 首先判断长度,s1的长度不能小于s2的长度
2 s1的排列有很多种,但是其中包含的字符都是一样的,我们在s2中取一个长度为s1的长度的滑动窗,每次看滑动窗内各个字符的数量是否都相等
可以用两个数组来存储字符的数量
就是将a-z出现的次数映射到数组的0-25

 int nums1[]=new int[26];
 int nums2[]=new int[26];
class Solution {
    public boolean checkInclusion(String s1, String s2) {
       if(s1.length()>s2.length())
          return false;      
        int nums1[]=new int[26];
        int nums2[]=new int[26];
        for(int i=0;i<s1.length();i++)
           nums1[s1.charAt(i)-'a']+=1;
        int left=0;int right=left+s1.length()-1;
      
        for(int i=left;i<=right;i++){
             nums2[s2.charAt(i)-'a']+=1;
          }          
        if(isEqual(nums1,nums2))
           return true;
        while(right<s2.length()){           
            nums2[s2.charAt(left)-'a']-=1;//left对应的字符移出滑动窗
            left++;
            right++; 
            if(right==s2.length()) //避免right越界
               return false;         
            nums2[s2.charAt(right)-'a']+=1;//right对应的字符加入滑动窗
            if(isEqual(nums1,nums2))
               return true;     
        }
        return false;

    }
    boolean isEqual(int arr1[],int arr2[]){
        for(int i=0;i<arr1.length;i++){
            if(arr1[i]!=arr2[i])
               return false;
        }
        return true;
    }
}

20210.08.09(第7天) 广度优先搜索 / 深度优先搜索

14 733. 图像渲染

有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。

给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。

为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。

最后返回经过上色渲染后的图像

这个题可以理解为在二维数组中,我们先指定其中一个元素A,我们希望把这个元素A的值改成我们新传入的值,

同时,与元素A在上下左右四个方向相邻的元素,如果他们的值与元素A的值相同,那么他们也会被修改为新的值

A在上下左右四个方向相邻的元素中,与元素A的值相同的元素 的 上下左右四个方向相邻的元素 如果值与元素A的值相同,也会被渲染

通俗一点就是,你在二维数组中通过上下左右四个方向移动找出所有路径,这些路径上的值都是元素A,然后把这些路径上的值都改成我们传入的新的值

在这里插入图片描述

这是因为修改后的值和修改前的值一样,比如(1,1)和(1,2)原来都是1,通过(1,1)把(1,2)改成了1,(1,2)又会反向遍历(1,1),(1,1)又会反向遍历(1,2),如滔滔江水,绵绵不绝,所以我们需要设置一个标志位,禁止反向遍历
在这里插入图片描述

class Solution {
    boolean visited[][];
    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
         int oldColor=image[sr][sc];
         visited=new boolean[image.length][image[0].length];
         dfs(image,sr, sc,  newColor,oldColor);
         return image;
    } 
    public void dfs(int[][] image, int sr, int sc, int newColor,int oldColor){
        if( sr<0||sr>image.length-1||sc<0||sc>image[0].length-1)
            return;
        if(image[sr][sc]==oldColor&&!visited[sr][sc]){
              image[sr][sc]=newColor; 
              visited[sr][sc]=true;//记录访问过的位置,防止重复访问造成无法退出循环
        }            
        else
          return ;      
        dfs(image,sr+1, sc,  newColor,oldColor) ;//向下
        dfs(image,sr-1, sc,  newColor,oldColor) ;//向上
        dfs(image,sr, sc+1,  newColor,oldColor) ;//向左
        dfs(image,sr, sc-1,  newColor,oldColor) ;//向右
    }
}

在这里插入图片描述

原来,当要修改的值和旧的值相同时,直接返回原来的数组就好了,不许要做改动。。。。

class Solution { 
    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
         int oldColor=image[sr][sc];       
         dfs(image,sr, sc,  newColor,oldColor);
         return image;
    } 
    public void dfs(int[][] image, int sr, int sc, int newColor,int oldColor){
        if( sr<0||sr>image.length-1||sc<0||sc>image[0].length-1||newColor==oldColor)
            return;
        if(image[sr][sc]==oldColor)
            image[sr][sc]=newColor;                              
        else
          return ;      
        dfs(image,sr+1, sc,  newColor,oldColor) ;//向下
        dfs(image,sr-1, sc,  newColor,oldColor) ;//向上
        dfs(image,sr, sc+1,  newColor,oldColor) ;//向左
        dfs(image,sr, sc-1,  newColor,oldColor) ;//向右
    }
}

在这里插入图片描述
进一步优化代码

class Solution {
    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
        int a=image[sr][sc];
        if(a!=newColor)
         changes(image,sr,sc,a,newColor);         
        return image;
    }
    public void changes(int[][] image,int i,int j,int a,int newColor){
        if(i<0||i==image.length||j<0||j==image[0].length||image[i][j]!=a){
            return;
        }
        image[i][j]=newColor;
        changes(image,i+1,j,a,newColor);
        changes(image,i-1,j,a,newColor);
        changes(image,i,j+1,a,newColor);
        changes(image,i,j-1,a,newColor);
    }
}

在这里插入图片描述

class Solution {
    int[] dx = {1, 0, 0, -1};
    int[] dy = {0, 1, -1, 0};

    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
        int currColor = image[sr][sc];
        if (currColor != newColor) {
            dfs(image, sr, sc, currColor, newColor);
        }
        return image;
    }

    public void dfs(int[][] image, int x, int y, int color, int newColor) {
        if (image[x][y] == color) {
            image[x][y] = newColor;
            for (int i = 0; i < 4; i++) {
                int mx = x + dx[i], my = y + dy[i];
                if (mx >= 0 && mx < image.length && my >= 0 && my < image[0].length) {
                    dfs(image, mx, my, color, newColor);
                }
            }
        }
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/flood-fill/solution/tu-xiang-xuan-ran-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

思路二:广度优先搜索
我们从给定的起点开始,进行广度优先搜索。每次搜索到一个方格时,如果其与初始位置的方格颜色相同,就将该方格加入队列,并将该方格的颜色更新,以防止重复入队。

注意:因为初始位置的颜色会被修改,所以我们需要保存初始位置的颜色,以便于之后的更新操作

class Solution {
    int[] dx = {1, 0, 0, -1};//用来标记x的移动方位
    int[] dy = {0, 1, -1, 0};//用来标记y的移动方位

    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
        int currColor = image[sr][sc];
        if (currColor == newColor) {
            return image;
        }
        int n = image.length, m = image[0].length;
        Queue<int[]> queue = new LinkedList<int[]>();
        queue.offer(new int[]{sr, sc});
        image[sr][sc] = newColor;
        while (!queue.isEmpty()) {
            int[] cell = queue.poll();
            int x = cell[0], y = cell[1];
            for (int i = 0; i < 4; i++) {
                int mx = x + dx[i], my = y + dy[i];
                if (mx >= 0 && mx < n && my >= 0 && my < m && image[mx][my] == currColor) {
                    queue.offer(new int[]{mx, my});
                    image[mx][my] = newColor;
                }
            }
        }
        return image;
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/flood-fill/solution/tu-xiang-xuan-ran-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

15 695. 岛屿的最大面积

给定一个包含了一些 0 和 1 的非空二维数组 grid 。

一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

在这里插入图片描述

思路:运用深度优先搜索
从网格中为1的点开始遍历,遍历它四个方向上1的个数,在遍历四个方向上为1的点的四个方向的1的个数…加起来

注意用过的点直接置零,防止重复遍历

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
       int max=0;
       for(int i=0;i<grid.length;i++){
           for(int j=0;j<grid[0].length;j++){
              if(grid[i][j]==1)
                 max=Math.max(max,dfs(grid,i,j));
           }
       }
       return max;
    }
    public int dfs(int[][] grid,int i,int j){
        if(i<0||i>grid.length-1||j<0||j>grid[0].length-1||grid[i][j]==0)
            return 0;
        grid[i][j]=0;
        return 1+dfs( grid,i+1,j)+dfs( grid,i-1,j)
        +dfs( grid,i,j+1)+dfs( grid,i,j-1);
    }
}

在这里插入图片描述在这里插入图片描述

思路2:广度优先搜索
在这里插入图片描述
我们把方法二中的栈改为队列,每次从队首取出土地,并将接下来想要遍历的土地放在队尾,就实现了广度优先搜索算法。

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        int ans = 0;
        for (int i = 0; i != grid.length; ++i) {
            for (int j = 0; j != grid[0].length; ++j) {
                int cur = 0;
                Queue<Integer> queuei = new LinkedList<Integer>();
                Queue<Integer> queuej = new LinkedList<Integer>();
                queuei.offer(i);
                queuej.offer(j);
                while (!queuei.isEmpty()) {
                    int cur_i = queuei.poll(), cur_j = queuej.poll();
                    if (cur_i < 0 || cur_j < 0 || cur_i == grid.length || cur_j == grid[0].length || grid[cur_i][cur_j] != 1) {
                        continue;
                    }
                    ++cur;
                    grid[cur_i][cur_j] = 0;
                    int[] di = {0, 0, 1, -1};
                    int[] dj = {1, -1, 0, 0};
                    for (int index = 0; index != 4; ++index) {
                        int next_i = cur_i + di[index], next_j = cur_j + dj[index];
                        queuei.offer(next_i);
                        queuej.offer(next_j);
                    }
                }
                ans = Math.max(ans, cur);
            }
        }
        return ans;
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/max-area-of-island/solution/dao-yu-de-zui-da-mian-ji-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。在这里插入代码片

2021.08.10 (第8天) 广度优先搜索 / 深度优先搜索

16 617. 合并二叉树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

class Solution {
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
         if(root1==null&&root2==null)
            return null;
        if(root1==null)
           return root2;
        if(root2==null)
         return root1;
         root1.val=root1.val+root2.val;
         root1.left=mergeTrees(root1.left,root2.left);
         root1.right = mergeTrees(root1.right, root2.right);
         return root1;
    }
}

在这里插入图片描述

思路2 :迭代
在这里插入图片描述

class Solution {
	public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
	//如果 t1和t2中,只要有一个是null,函数就直接返回
		if(t1==null || t2==null) {
			return t1==null? t2 : t1;
		}
		java.util.LinkedList<TreeNode> queue = new java.util.LinkedList<TreeNode>();
		queue.add(t1);
		queue.add(t2);
		while(queue.size()>0) {
			TreeNode r1 = queue.remove();
			TreeNode r2 = queue.remove();
			r1.val += r2.val;
			//如果r1和r2的左子树都不为空,就放到队列中
			//如果r1的左子树为空,就把r2的左子树挂到r1的左子树上
			if(r1.left!=null && r2.left!=null){
				queue.add(r1.left);
				queue.add(r2.left);
			}
			else if(r1.left==null) {
				r1.left = r2.left;
			}
			//对于右子树也是一样的
			if(r1.right!=null && r2.right!=null) {
				queue.add(r1.right);
				queue.add(r2.right);
			}
			else if(r1.right==null) {
				r1.right = r2.right;
			}
		}
		return t1;
	}
}


作者:wang_ni_ma
链接:https://leetcode-cn.com/problems/merge-two-binary-trees/solution/dong-hua-yan-shi-di-gui-die-dai-617he-bing-er-cha-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

17 116. 填充每个节点的下一个右侧节点指针

在这里插入图片描述

解题思路
需要找到两个结点,让结点1的右子节点的next指向结点2的左子节点

class Solution {
    public Node connect(Node root) {
         if(root==null)
           return null;
        conn( root.left,root.right); 
        return root;
    }
    public void conn(Node root1,Node root2){
        if(root1==null||root2==null)
           return ;
        root1.next=root2;
        conn( root1.left,root1.right);
        conn( root1.right,root2.left);
        conn( root2.left,root2.right);
    }
}

思路2
我们的难点是要找到右节点的next,可以充分利用根节点的nex是否为空这个条件

class Solution {
    public Node connect(Node root) {
        if(root==null){
            return root;
        }
        if(root.left!=null){
            root.left.next=root.right;
            root.right.next=root.next!=null?root.next.left:null;
            connect(root.left);
            connect(root.right);
        }
        return root;
    }
}

思路3:迭代

思路4:队列,层序遍历

class Solution {
    public Node connect(Node root) {
        if (root == null) {
            return root;
        }
        
        // 初始化队列同时将第一层节点加入队列中,即根节点
        Queue<Node> queue = new LinkedList<Node>(); 
        queue.add(root);
        
        // 外层的 while 循环迭代的是层数
        while (!queue.isEmpty()) {
            
            // 记录当前队列大小
            int size = queue.size();
            
            // 遍历这一层的所有节点
            for (int i = 0; i < size; i++) {
                
                // 从队首取出元素
                Node node = queue.poll();
                
                // 连接
                if (i < size - 1) {
                    node.next = queue.peek();
                }
                
                // 拓展下一层节点
                if (node.left != null) {
                    queue.add(node.left);              
                    queue.add(node.right);
                }
            }
        }
        
        // 返回根节点
        return root;
    }
}

参考:116. 填充每个节点的下一个右侧节点指针

2021.08.11(第9天)广度优先搜索 / 深度优先搜索

18 542. 01 矩阵

此题我单独写了一篇博客

leetcode 542. 01 矩阵(Java DFS+BFS+DP)

19 994. 腐烂的橘子

在这里插入图片描述

所谓广度优先搜索,就是从起点出发,每次都尝试访问同一层的节点,如果同一层都访问完了,再访问下一层,最后广度优先搜索找到的路径就是从起点开始的最短合法路径

观察到对于所有的腐烂橘子,其实它们在广度优先搜索上是等价于同一层的节点的

解题思路:用广度优先搜索,从所有为2的点开始扩散,然后把被第一轮扩散而腐烂的橘子加入队列,用第一轮被腐烂的橘子发起第二轮扩散,直到没有入队的腐烂橘子,最后再检查矩阵中还有没有没有腐烂的橘子

class Solution {
    public int orangesRotting(int[][] grid) {
      //我所理解的本题就是在矩阵中以2开始,找一条路径上面全是1,求这个全是1的最大长度
      //理解错了,应该是广搜,从所有为2的点开始扩散
        Queue<int[]>queue=new LinkedList();
        int m=grid.length;
        int n=grid[0].length;
        int min=0;//最小时间
        int OneCount=0;//计数1的个数
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]==2)
                  queue.offer(new int[]{i,j});//把所有腐烂的橘子入队
                if(grid[i][j]==1)
                   OneCount++;
            }
        }
        if(OneCount==0)//原来没有腐烂的橘子
            return 0;
        while(!queue.isEmpty()){           
            int dx[]={-1,0,0,1};
            int dy[]={0,1,-1,0};
            int size=queue.size();
             min++;//计数扩散的次数(第一轮扩散是从所有为2的点开始的,所以要弹出size次,第二轮应该把上一轮新腐烂的橘子弹出队列....)
            for(int k=0;k<size;k++){ 
             int []arr=queue.poll();
                for(int i=0;i<4;i++){
              int newX=arr[0]+dx[i];
              int newY=arr[1]+dy[i];
              if(newX>=0&&newX<m&&newY>=0&&newY<n&& grid[newX][newY]==1){
                 grid[newX][newY]=2; 
                 queue.offer(new int[]{newX,newY});//新腐烂的橘子入队
              }              
            }                  
            }                     
        }
        //检查bfs结束后还有没有没有腐烂的橘子
         for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]==1)
                  return min=-1; //如果bfs结束,矩阵中还有为1的点,说明有永远不会腐烂的橘子,返回-1                
            }
        }
        return min-1;//因为最后一轮扩散的橘子也会被入队,所以多扩散了一次,需要减去一次
    }
}

在这里插入图片描述

20210.08.12 (第10天) 递归 / 回溯

20 21. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

思路1 迭代

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode head=new ListNode(0);
        ListNode temp=head;
        while(l1!=null&&l2!=null){
            if(l1.val<=l2.val){
                temp.next=l1;
                l1=l1.next;
            }
            else{
                temp.next=l2;
                l2=l2.next;
            }
            temp=temp.next;
        }
        if(l1==null)
          temp.next=l2;
        else
         temp.next=l1;
       return head.next;
    }
}

思路2 递归

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1==null)
            return l2;
        if(l2==null)
            return l1;
        if(l1.val<=l2.val)                   
             l1.next=mergeTwoLists(l1.next,l2);                  
        else          
             l2.next =mergeTwoLists(l1,l2.next);                  
        return l1.val<=l2.val?l1:l2;
    }
}

21 206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

思路1:迭代,利用假头

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode dummy=new ListNode(0);
        ListNode cur=head;
        ListNode end=null;
        while(cur!=null){
            end=dummy.next;
            dummy.next=cur;
            cur=cur.next;
            dummy.next.next=end;
        }
        return dummy.next;
    } 
}

迭代,不用假头

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }
}


作者:LeetCode
链接:https://leetcode-cn.com/problems/reverse-linked-list/solution/fan-zhuan-lian-biao-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

思路2 递归

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null||head.next==null)
          return head;        
        ListNode result=reverseList(head.next);
        head.next.next=head;
        head.next=null;
        return result;
    }
}

2021.08.13 第11天 递归 / 回溯

22 77. 组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

思路:这个题想的挺波折的,一开始,就在combine里面整理个for循环调用回溯,结果很多重复答案
然后就是最先用的list,然后list满k就直接把list放进去result,然后从list里面删除最后一个元素,哎,天真了,在加入result的时候应该是加入new ArrayList(list),不然后面对list的操作还是会影响已经加入了result里面的list

于是我改成了stack,不过每次也需要把stack转成一个新的Arraylist

 class Solution {
        List<List<Integer>>result=new ArrayList();
        Stack<Integer>stack=new Stack();
        public List<List<Integer>> combine(int n, int k) {
           // for(int i=1;i<=n;i++){
            //    stack.clear();
                backTrack(1, n,k);
          //  }
            return result;
        }
        public void backTrack(int s,int n,int k){
            if( stack.size()==k){
                result.add(new ArrayList(stack));
              //  stack.pop();
                return;//即使止损,不然会把1234加进去在弹出4弹出3
            }
            for(int i=s;i<=n;i++ ){
                stack.add(i);
                backTrack(i+1, n,k);
                stack.pop();
            }
        }
    }

在这里插入图片描述

是不是stack转成list太费时间了?我感觉我的也不需要剪枝啊。
于是改成list

 class Solution {
        List<List<Integer>>result=new ArrayList();
        List<Integer>list=new ArrayList();
        public List<List<Integer>> combine(int n, int k) {  
            backTrack(1, n,k);
            return result;
        }
        public void backTrack(int s,int n,int k){
            if( list.size()==k){
                result.add(new ArrayList(list)); 
                return;//及时止损,不然会把1234加进去再弹出4弹出3
            }
            for(int i=s;i<=n;i++ ){
                list.add(i);
                backTrack(i+1, n,k);
                list.remove(list.size()-1);
            }
        }
    }

还是不太让人满意啊
在这里插入图片描述

论剪枝的必要性
在这里插入图片描述

哈哈哈,剪枝大法,太优秀了,yyds

 class Solution {
        List<List<Integer>>result=new ArrayList();
        List<Integer>list=new ArrayList();
        public List<List<Integer>> combine(int n, int k) {  
            backTrack(1, n,k);
            return result;
        }
        public void backTrack(int s,int n,int k){
            if( list.size()==k){
                result.add(new ArrayList(list)); 
                return;//及时止损,不然会把1234加进去再弹出4弹出3
            }
            for(int i=s;i<=n;i++ ){               
                if(k-list.size()>n-i+1)//如果k减去当前list的长度,剩下的就是还需要加入的数量,i到n的数量为n-i+1,如果k-list.size()>n-i+1,直接返回
                   return ;
                list.add(i);
                backTrack(i+1, n,k);
                list.remove(list.size()-1);
                 //path.remove((Integer) i);不然要这样写    才能能去除i
            }
        }
    }

注意

 for(int i=s;i<=n;i++ ){               
                if(k-list.size()>n-i+1)//如果k减去当前list的长度,剩下的就是还需要加入的数量,i到n的数量为n-i+1,如果k-list.size()>n-i+1,直接返回
                   return ;
                list.add(i);
                backTrack(i+1, n,k);
                list.remove(list.size()-1);
                 //path.remove((Integer) i);不然要这样写    才能能去除i
            }

可以改成

  for (int i = s; i <= n - (k - path.size()) + 1; i++) { // 优化的地方
                list.add(i);
                backTrack(i+1, n,k);
                list.remove(list.size()-1);
        }

在这里插入图片描述

再贴一个1ms的

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> combine(int n, int k) {
        combineHelper(n - k + 1, k, 1);
        return result;
    }

    /**
     * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex
     * @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
     */
    private void combineHelper(int n, int k, int startIndex){
        //终止条件
        if (path.size() == k){
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = startIndex; i <= (n + path.size()); i++){
            path.add(i);
            combineHelper(n, k, i + 1);
            path.removeLast();
        }
    }
}

参考 组合–回溯法的剪枝思路
「代码随想录」带你学透回溯算法!【77. 组合】
另外 ,是可以用位运算的

23 46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

在这里插入图片描述

解题思路
依次把第一位固定为nums[0],nums[1],nums[2]…在后面的递归过程中,如果发现list里面已经加入了同样的元素,就直接跳过

class Solution {
    List<List<Integer>>result=new ArrayList();
    List<Integer>list=new ArrayList();
    public List<List<Integer>> permute(int[] nums) {
        int n=nums.length;
       for(int i=0;i<nums.length;i++)
           backTrack(i, n,nums);
        return result;
    }
    public void backTrack(int s ,int n,int nums[]){
       if(list.size()==n) {
           result.add(new ArrayList(list));
           return ;
       }       
        for(int i=s;i<n;i++){
          while(i<nums.length&&list.contains(nums[i]))
               i++;
            if(i>=nums.length)
               return ;
           list.add(nums[i]);            
           backTrack(s, n,nums);
           list.remove((Integer)nums[i]);
        }
    }
}

在这里插入图片描述

但是经过分析发现,permute里面的for循环根本就不需要,因为其他的几个for循环根本就不会满足list.size()==n这个条件,所以只是无意义的判断,可以去掉

 public List<List<Integer>> permute(int[] nums) {
        int n=nums.length;
      // for(int i=0;i<nums.length;i++)
        backTrack(i, n,nums);
        return result;
    }

在这里插入图片描述

对于已经使用过的元素的判断,我们可以用visited数组

class Solution {
    List<List<Integer>>result=new ArrayList();
    List<Integer>list=new ArrayList();
    boolean[]visited;
    public List<List<Integer>> permute(int[] nums) {
        int n=nums.length;  
        visited=new boolean[n];
        backTrack(0, n,nums);
        return result;
    }
    public void backTrack(int s ,int n,int nums[]){
       if(list.size()==n) {
           result.add(new ArrayList(list));
           return ;
       }       
        for(int i=s;i<n;i++){
           if(visited[i])
               continue;
           visited[i]=true;
           list.add(nums[i]);            
           backTrack(s, n,nums);
           visited[i]=false;
           list.remove((Integer)nums[i]);
        }
    }
}

在这里插入图片描述

理解回溯
在这里插入图片描述

24 784. 字母大小写全排列

给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。

在这里插入图片描述
在这里插入图片描述

解题思路:从空字符船开始,遇到数字,就直接拼接,遇到字母,就把原来已有的结果复制一份,然后分别拼接这个字母的大写和小写

涉及大量字符串拼接用StringBuilder
在这里插入图片描述

class Solution {
    public List<String> letterCasePermutation(String s) {
       List<StringBuilder>ans=new ArrayList();
       ans.add(new StringBuilder());
       for(char c:s.toCharArray()){
           int n=ans.size();
           if(Character.isLetter(c)){
               for(int i=0;i<n;i++){
               ans.add(new StringBuilder(ans.get(i)));//将现有的结果字符串都复制一份
               ans.get(i).append(Character.toLowerCase(c));
               ans.get(i+n).append(Character.toUpperCase(c));
           }
           }
           else {
               for(int i=0;i<n;i++)           
                    ans.get(i).append(c);                        
           }
       
       }
        List<String> finalans = new ArrayList();
        for (StringBuilder sb: ans)
            finalans.add(sb.toString());
        return finalans;
    }
}

在这里插入图片描述

思路2:回溯

class Solution {

    private List<String> res = new ArrayList<>();
    public List<String> letterCasePermutation(String s) {

        StringBuilder sb = new StringBuilder();

        backtrack(s, 0, sb);
        return res;
    }

    public void backtrack(String s, int start, StringBuilder sb) {

        if (sb.length() == s.length()) {

            res.add(sb.toString());
            return ;
        }

        for (int i=start;i<s.length();i++) {
              if(sb.length()<i)//剪枝,效率更高,如果start到s.length的长度小于当前还需要的字符数量,直接返回
                 return ;
             
            char c = s.charAt(i);

            if (Character.isDigit(c)) {

                sb.append(c);
                backtrack(s, i + 1, sb);
                sb.deleteCharAt(sb.length() - 1); // 撤销选择
            } else {

                char A = Character.toUpperCase(c);
                sb.append(A);
                backtrack(s, i + 1, sb);
                sb.deleteCharAt(sb.length() - 1);

                char a = Character.toLowerCase(c);
                sb.append(a);
                backtrack(s, i + 1, sb);
                sb.deleteCharAt(sb.length() - 1);
            }
        }
    }
}

在这里插入图片描述

看一个1ms的

class Solution {
    List<String> res = new ArrayList<>();
    public List<String> letterCasePermutation(String s) {
        char[] chs = s.toCharArray();
        int n = chs.length;
        dfs(chs, n, 0);
        return res;
    }

    private void dfs(char[] chs, int n, int begin) {
        res.add(new String(chs));//每次都把判断完是数保存起来,一开始就把初始值保存了
        for(int i=begin;i<n;i++){
            if(Character.isLetter(chs[i])){//判断是否为字符,是就对它进行操作
                char temp = chs[i];//每个字母都有大写与小写两种情况,比如:在把小写改为大写判断情况时,原来的小写要暂时保存起来,因为后面要接着对这种情况来判断
                chs[i] = (char)(chs[i] - 'a' >= 0 ? chs[i] - 32 : chs[i] + 32);//判断是大写还是小写,是大写就改为小写,是小写就改为大写
                dfs(chs,n,i+1);//递归循环判断
                chs[i] = temp;//比如:当第一个字母为大写的情况全判断完后(Abc,AbC,ABc,ABC),接下来就对第一个字母为小写的情况判断(abc,abC,aBc,aBC)
            }
        }
    }
}

在这里插入图片描述
在这里插入图片描述
注意字符串的生成顺序,看看是怎么运行的
在这里插入图片描述

2021.08.14 第12天 动态规划

25 70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

解题思路:最后一步可以由倒数第一步跳一步得到,也可以由倒数第二步调两步得到,f(n)=f(n-1)+f(n-2)

class Solution {  
    public int climbStairs(int n) {
      if(n<3)
        return n;
      int a=1,b=2,c=0;
       for(int i=3;i<=n;i++){
          c=a+b;
          a=b;
          b=c;
       }
       return c;
    }   
}

26 198. 打家劫舍

在这里插入图片描述

解题思路:分为偷当前和不偷当前两个数组计算

class Solution {
    public int rob(int[] nums) {
        int dp1[]=new int[nums.length];//偷当前能得到的最大值
        int dp2[]=new int[nums.length];//不偷当前能得到的最大值
        dp1[0]=nums[0];
        dp2[0]=0;
        for(int i=1;i<nums.length;i++){
            dp1[i]=dp2[i-1]+nums[i];//偷当前,只能是前一家没有偷
            dp2[i]=Math.max(dp2[i-1],dp1[i-1]);//不偷当前,可能是前一家偷了,也可能是没有偷前一家
        }
        return Math.max(dp1[nums.length-1],dp2[nums.length-1]);
    }
}

思路2
在这里插入图片描述

class Solution {
    public int rob(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int length = nums.length;
        if (length == 1) {
            return nums[0];
        }
        int[] dp = new int[length];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);
        for (int i = 2; i < length; i++) {
            dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[length - 1];
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/house-robber/solution/da-jia-jie-she-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
    public int rob(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int length = nums.length;
        if (length == 1) {
            return nums[0];
        }
        int first = nums[0], second = Math.max(nums[0], nums[1]);
        for (int i = 2; i < length; i++) {
            int temp = second;
            second = Math.max(first + nums[i], second);
            first = temp;
        }
        return second;
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/house-robber/solution/da-jia-jie-she-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

27 120. 三角形最小路径和

在这里插入图片描述

解题思路1 dfs遍历(超时)

class Solution {

    public int minimumTotal(List<List<Integer>> triangle) {
       return dfs(triangle,0,0);   
    }
    public int dfs(List<List<Integer>> triangle,int i,int j){
        if(i>triangle.size()-1)
           return 0;        
        return Math.min(dfs(triangle, i+1, j),dfs(triangle, i+1, j+1))+triangle.get(i).get(j);
    }
}

记忆化搜索
实在是太快了

class Solution {
    int[][]visited;
    public int minimumTotal(List<List<Integer>> triangle) {
        visited=new int [triangle.size()][triangle.size()];
        for(int i=0;i<triangle.size();i++)
         Arrays.fill(visited[i],Integer.MAX_VALUE);
       return dfs(triangle,0,0);   
    }
    public int dfs(List<List<Integer>> triangle,int i,int j){
        if(i>triangle.size()-1)
           return 0;  
        if(visited[i][j]!=Integer.MAX_VALUE)    
          return   visited[i][j];
        visited[i][j]= Math.min(dfs(triangle, i+1, j),dfs(triangle, i+1, j+1))+triangle.get(i).get(j);
        return  visited[i][j];
    }
}

在这里插入图片描述

用Integer做缓存数组,感觉更巧妙,判空就知道有没有缓存的值了

class Solution {
     Integer[][] memo;
    public int minimumTotal(List<List<Integer>> triangle) {        
          memo = new Integer[triangle.size()][triangle.size()];
          return dfs(triangle,0,0);
    }
    public  int dfs(List<List<Integer>> triangle,int i,int j)
    {
       if(i==triangle.size())
       {
           return 0;
       }      
        if (memo[i][j] != null) {
            return memo[i][j];
        }
        return memo[i][j] = Math.min(dfs(triangle, i + 1, j), dfs(triangle, i + 1, j + 1)) + triangle.get(i).get(j);
    }
}

思路2 动态规划
从下往上,从前往后求dp数组,dp[][]表示当前位置到底部的最小距离

class Solution {
    int[][]visited;
    public int minimumTotal(List<List<Integer>> triangle) {
        int dp[][]=new int [triangle.size()][triangle.size()];
        for(int j=0;j<triangle.size();j++)
          dp[triangle.size()-1][j]=triangle.get(triangle.size()-1).get(j);//填充最后一行dp
        for(int i=triangle.size()-2;i>=0;i--){
            for(int j=0;j<=i;j++){
                dp[i][j]=Math.min(dp[i+1][j],dp[i+1][j+1])+triangle.get(i).get(j);
            }
        }
        return dp[0][0];
    }
}

增加最后一行,简化代码

class Solution {  
    public int minimumTotal(List<List<Integer>> triangle) {        
        int M=triangle.size();
        int N=triangle.get(M-1).size();//注意这里,,,
       //用二维dp数组代表点[i][j]到底边的最小距离
        int dp[][]=new int [M+1][N+1];//用来避免最后一行算的时候越界     
        for (int i = M - 1; i >= 0; i--)
         {
           for(int j=0;j<=i;j++)
           {
               dp[i][j]=Math.min(dp[i+1][j],dp[i+1][j+1])+triangle.get(i).get(j);
           }
       }
        return dp[0][0];
    } 
}

也可以从上往下遍历,dp[][]表示走到当前点所用的最小距离

class Solution {  
    public int minimumTotal(List<List<Integer>> triangle) {        
        int M=triangle.size();
        int N=triangle.get(M-1).size();//注意这里,,,
       //用二维dp数组代表点[i][j]到底边的最小距离
        int dp[][]=new int [M][N];     
         dp[0][0]=triangle.get(0).get(0);
          if(M==1) return dp[0][0];
         for(int i=1;i<M;i++)
         {
             dp[i][0]=dp[i-1][0]+triangle.get(i).get(0);//每一行的最左一列只能从上一行的最左一列走到
             for(int j=1;j<i;j++)
             {
                 dp[i][j]=Math.min(dp[i-1][j],dp[i-1][j-1])+triangle.get(i).get(j);//每一行中间的分别可以由上一行的两个列得到
             }
             dp[i][i]=dp[i-1][i-1]+triangle.get(i).get(i);//每一行的最后一列只能从上一行的最后一列得到
         }
        int min=Integer.MAX_VALUE;
        for(int i=0;i<N;i++)
        {
           min=Math.min(min,dp[M-1][i]) ;
        }
         return   min;  
    } 
}

2021.08.15 第13天 位运算

28 231. 2 的幂

给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。

解题思路:循环取出n的每一个二进制位,如果为1,就计数加1,计数超过1,直接退出循环,返回false

2 的幂次方的二进制位只可能有1个1,比如1024,是由512左移一位得到,512之前所有的二进制位为1加起来也只有511(011111111),512+511=1023(111111111)根本不够1024(1111111111)

class Solution {
    public boolean isPowerOfTwo(int n) {
        int count=0;
        while(n>0&&count<2){
            if((n&1)==1)
               count++;
            n=n>>1;            
        }
        return count==1;
    }
}
class Solution {
    public boolean isPowerOfTwo(int n) {
          while(n>1){
              if((n&1)==1)
                return false;
              n=n>>1;
          }
          return n==1;
    }
}

能不能不用循环和递归?

在这里插入图片描述

class Solution {
    final int BIG = 1 << 30;
    public boolean isPowerOfTwo(int n) {          
    return n > 0 && BIG % n == 0;    
     }
}

在这里插入图片描述
在一些语言中,位运算的优先级较低,需要注意运算顺序

class Solution {  
    public boolean isPowerOfTwo(int n) {          
    return n > 0 &&(n&n-1) == 0;    
    }
}

在这里插入图片描述

class Solution {  
    public boolean isPowerOfTwo(int n) {          
    return n > 0 &&(n&-n) == n;    
    }
}

29 191. 位1的个数

计算机里的数是用二进制表示的,最左边的这一位一般用来表示这个数是正数还是负数,这样的话这个数就是有符号整数。如果最左边这一位不用来表示正负,而是和后面的连在一起表示整数,那么就不能区分这个数是正还是负,就只能是正数,这就是无符号整数

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
         int count=0;
         for(int i=0;i<32;i++){
             if((n&1)==1)
               count++;
             n=n>>1;
         }
         return count;
    }
}
public class Solution {
    public int hammingWeight(int n) {
        int ret = 0;
        for (int i = 0; i < 32; i++) {
            if ((n & (1 << i)) != 0) {
                ret++;
            }
        }
        return ret;
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/number-of-1-bits/solution/wei-1de-ge-shu-by-leetcode-solution-jnwf/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

方法二:位运算优化

public class Solution {
    public int hammingWeight(int n) {
        int ret = 0;
        while (n != 0) {
            n &= n - 1;
            ret++;
        }
        return ret;
    }
}

在这里插入图片描述
在这里插入图片描述

2021.08.16 第14天 位运算

30 190. 颠倒二进制位

颠倒给定的 32 位无符号整数的二进制位。

解题方法1
每次取出二进制的最后一位加到结果中

public class Solution {
    // you need treat n as an unsigned value
    public int reverseBits(int n) {
       int result=0;
       for(int i=0;i<32;i++){
           result=(result<<1)+(n&1);
           n=n>>1;
       }
       return result;
    }
}
public class Solution {
    public int reverseBits(int n) {
        int rev = 0;
        for (int i = 0; i < 32 && n != 0; ++i) {
            rev |= (n & 1) << (31 - i);
            n >>>= 1;
        }
        return rev;
    }
}

31 136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

解题思路,用异或,两个相同的数异或结果为0

class Solution {
    public int singleNumber(int[] nums) {
         int n=0;
         for(int i=0;i<nums.length;i++){
             n=n^nums[i];//异或,两个相同的数异或结果为0
         }
         return n ;
    }
}

在这里插入图片描述

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值