LeetCode Hot 100

LeetCode Hot 100

刷题记录 LeetCode Hot 100

1. 两数之和

代码思路

  • 将出现过的值存储在Map中
  • 若当前遍历循环到num,那么只需要在map中找到是否曾经遍历过targer-num,即可找到答案
  • 总而言之,维护一个[过去存在的]值的映射
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> dic = new HashMap<>();
        int n = nums.length;
        for(int i=0;i<n;i++)
        {
            int find = target - nums[i];
            if(dic.containsKey(find)) return new int[]{i,dic.get(find)};
            dic.put(nums[i],i);
        }
        return new int[]{-1,-1};
    }
}

2. 两数相加

代码思路: 主要考察数值相加,主要是要记得更新进位数值,以及记得进位末尾添加

  • 遍历L1,L2节点,若节点为空,则用0填充值
    • 主要是为了看起来简约,其实都是N的时间复杂度,就不搞那么麻烦了
  • 构建当前节点,更新进位
  • 最后查看进位是否余存
  • 返回头节点
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = new ListNode();
        ListNode cur = head;
        int carry=0,a=0,b=0;
        while(l1!=null || l2!=null)
        {
            a=0;
            b=0;
            if(l1!=null)
            {
                a = l1.val;
                l1 = l1.next;
            }
            if(l2!=null)
            {
                b = l2.val;
                l2 = l2.next;
            }
            int curval = (a+b+carry)%10;
            carry = (a+b+carry)/10;
            cur.next = new ListNode(curval,null);
            cur = cur.next;
        }
        if(carry!=0) cur.next = new ListNode(carry);
        return head.next;
    }
}

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

代码思路

  • 模拟:由于窗口内的值都是不重复的,可以用set模拟窗口大小
  • 双指针:用left表示窗口最左指针,right表示正进入窗口的指针
    • 当right进入窗口前,判断窗口(set)是否存在该[r]元素
      • 若存在,则left指针移动(模拟窗口移动),将[l]元素移出窗口,直到窗口内不存在需要加入的[r]
    • 窗口内,每次添加[r]元素
    • 记录set.size()最大值
  • 返回max
class Solution {
    public int lengthOfLongestSubstring(String s) {
        int l=0,n=s.length();
        Set<Character> set = new HashSet<>();
        int max = 0;
        for(int r=0;r<n;r++)
        {
            char cur = s.charAt(r);
            while(!set.isEmpty() && set.contains(cur)) set.remove(s.charAt(l++));
            set.add(cur);
            max = Math.max(set.size(),max);
        }
        return max;
    }
}

4. 寻找两个正序数组的中位数

代码思路:

  • 将两个数组合并,并找出中位数
    • 若数组长度为偶数,需要找到mid = n/2后,继续寻找mid-1的元素
      • 如n=4,mid=2 --> mid-1=1
  • 找中位数采用快排的思路,加速寻找
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n = nums1.length,m=nums2.length,index=0;
        int total = n+m;
        int a=0,b=0;
        int[] res = new int[total];
        int l=0,r=total-1,tar = total/2;
        for(int num : nums1) res[index++]=num;
        for(int num : nums2) res[index++]=num;
        while(l<=r)
        {
            int cur = partition(res, l, r);
            if(cur==tar)
            {
                if(total%2==0)
                {
                    a = res[cur];
                    break;
                }
                return res[cur];
            }
            if(cur>tar)
            {
                r = cur-1;
            }else l = cur+1;
        }
        tar--;
        l = 0;
        r = tar;
        while(l<=r)
        {
            int cur = partition(res, l, r);
            if(cur==tar)
            {
                
                return (res[cur]+a)/2.0;
            }
            if(cur>tar)
            {
                r = cur-1;
            }else l = cur+1;
        }
        return 0;
    }
    int partition(int[] arr , int lo,int ro)
    {
        if(lo>ro) return -1;
        int l=lo,r=ro,divi=arr[lo];
        while(true)
        {
            while(l<=r && arr[l]<=divi) l++;
            while(l<=r && arr[r]>divi) r--;
            if(l>r) break;
            swap(arr,l,r);
        }
        swap(arr,lo,r);
        return r;
    }
    void swap(int[] arr,int r,int l)
    {
        int temp = arr[l];
        arr[l]=arr[r];
        arr[r]=temp;
    }
}

5. 最长回文子串

代码思路:

  • 这个问题有著名算法【Manacher 算法】,但是如果是做题,没有学习过该算法,基本写不出来,所有使用动态规划的思路解决

  • 动态规划

    bp[i][j] : 表示str[i,j]是否是回文串,长度为len
    当[i]==[j],bp[i][j]只由bp[i+1][j-1]决定
    初始化: bp[0][0]=true,表示空串默认为回文串
    
    区间DP:以长度循环条件,遍历左边界-->确定右边界
      为了不进行判断,当[i]==[j](元素相同),区间长度小于4,bp[i][j] = true
    
class Solution {
    boolean[][] bp;
    int n;
    public String longestPalindrome(String s) {
        if(s==null) return "";
        this.n = s.length();
        int maxL=0;
        int[] max = new int[2];
        bp = new boolean[n+1][n+1];
        for(int len=1;len<=n;len++)
        {
            for(int i=0;i<=n-len;i++)
            {
                int j = i+len-1;
                if(s.charAt(i)==s.charAt(j))
                {
                    if(len<=3) bp[i][j]=true;
                    else bp[i][j] = bp[i+1][j-1];
                    if(bp[i][j])
                    {
                        if(maxL<len)
                        {
                            maxL = len;
                            max[0]=i;
                            max[1]=j;
                        }
                        
                    }
                }
            }
        }
        if(maxL==0) return "";
        return s.substring(max[0],max[1]+1);
    }
}

10. 正则表达式匹配

代码思路:p是模式串,s是待匹配串

  • 动态规划,主要注意点在于如何初始化bp
    • 当p长度为0,结果一定是false
    • 当s长度为0,结果不一定为false,这是p中的*是可以使得p删除前一个元素,使得p表示空字符
若s[i-1]==p[j-1] or p[j-1]=='.' bp[i][j] = bp[i-1][j-1];
若p[j]=='*' and j-2>=0;
	bp[i][j] = bp[i][j-2]; // 删除
	若s[i-1]==p[j-2] bp[i][j] |= bp[i-1][j]; // 当*可以匹配,则可以匹配多个,相当于没匹配不变,i-1
class Solution {
    public boolean isMatch(String s, String p) {
        int n = s.length(),m=p.length();
        boolean[][] bp = new boolean[n+1][m+1];
        bp[0][0] = true;
        for(int i=0;i<=n;i++)
        {
            for(int j=0;j<=m;j++)
            {
                if(j==0)
                {
                    continue;
                }
                if(i==0)
                {
                    if(p.charAt(j-1)=='*' && j-2>=0) bp[i][j] = bp[i][j-2];
                    continue;
                }
                char curs = s.charAt(i-1),curp=p.charAt(j-1);
                if(curs==curp || curp=='.')
                {
                    bp[i][j] = bp[i-1][j-1];
                }else if(curp=='*' && j-2>=0){
                    bp[i][j] = bp[i][j-2];
                    if(p.charAt(j-2)=='.' || p.charAt(j-2)==curs)
                    {
                        bp[i][j] |= bp[i-1][j];
                    }
                }
            }
        }
        return bp[n][m];
    }
}

11. 盛最多水的容器

代码思路:双指针

  • 左右指针向中间靠拢,计算左右指针能构成的矩形 -> s = (r-l)*min{h[l],h[r]}
  • 每次,移动高度较小的指针,宽度为0时直到指针相遇
class Solution {
    public int maxArea(int[] height) {
        int l=0,r=height.length-1;
        int max=0;
        while(l<r)
        {
            max = Math.max(max,(r-l)*Math.min(height[r],height[l]));
            if(height[l]>height[r]) r--;
            else l++;
        }
        return max;
    }
}

15. 三数之和

经典降低维度的思路,将三数之和,降低为俩数之和,注意点在于去重

  • 去重
    • 首先将数组排序
      • 第一个数去重,同时,可以使用双指针,缩小答案范围
    • 由于三数之和必须为0,所以确定两个数,就能唯一确定第三个数。当第二个数重复时,第三个数也一定重复,那么,在寻找到合适答案组合后,需要去元素进行去重,也就是左右指针分别找到与答案组不同的值
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        for(int i=0;i<=n-3;i++)
        {
            if(i!=0 && nums[i]==nums[i-1]) continue;
            int find = -nums[i];
            int l=i+1,r=n-1;
            while(l<r)
            {
                int cur = nums[l]+nums[r];
                if(cur==find)
                {
                    List<Integer> path = new ArrayList<>();
                    path.add(nums[i]);path.add(nums[l]);path.add(nums[r]);
                    res.add(path);
                    l++;r--;// 左右指针分别探测
                    // 若与答案组元素相同,则跳过
                    while(l<r && nums[l]==nums[l-1]) l++;
                    while(l<r && nums[r]==nums[r+1]) r--;
                }else if(cur>find)
                {
                    r--;
                }else l++;
            }
        }
        return res;
    }
}

17. 电话号码的字母组合

代码思路

  • 递归问题,主要考虑
    • 出口
    • 树层(当前可以选择的元素集合)
    • 树高
    • 处理和回溯
  • 出口:当路径上的元素个数==str的长度,证明已找到叶子节点
  • 树层:每一层树层都是对应字符串下标所表示的字符串
  • 树高:本题为字符串长度
  • 处理:每次选择一个元素,记录路径,进入递归
  • 回溯:将路径值弹出
class Solution {
    static HashMap<Integer,String> dic = new HashMap<>(16);
    static
    {
        dic.put(2,"abc");
        dic.put(3,"def");
        dic.put(4,"ghi");
        dic.put(5,"jkl");
        dic.put(6,"mno");
        dic.put(7,"pqrs");
        dic.put(8,"tuv");
        dic.put(9,"wxyz");
    }
    List<String> res = new LinkedList<>();//结果集
    StringBuilder path = new StringBuilder();//路径
    String digits;
    int n;
    public List<String> letterCombinations(String digits) {
        if(digits==null || digits.length()==0) return res;
        this.digits = digits;
        this.n = digits.length();
        dsf();
        return res;
    }
    void dsf()
    {
        if(path.length()==n)
        {
            res.add(path.toString());
            return;
        }
        // 下标其实就是路径的长度
        String cur = dic.get(digits.charAt(path.length())-'0');
        for(int i=0;i<cur.length();i++)
        {
            path.append(cur.charAt(i));
            dsf();
            // path.delete(path.length()-1,path.length());
            path.setLength(path.length()-1);
        }

    }
}

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

代码思路:

  • 通过虚拟头节点删除倒数第N个节点
  • 倒数第N个节点其实就是正向链表长度Len-N+1个节点
  • 虚拟节点向后移动Len-N个节点,到达第Len-N+1个节点的pre节点,链表元素删除即可
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(head==null) return null;
        ListNode pre = new ListNode(-1,head);
        ListNode cur = pre;
        int len = getLen(head);
        for(int i=0;i<len-n;i++)
        {
            if(cur==null) return head;
            cur = cur.next;
        }
        if(cur==null || cur.next==null) return head;
        cur.next = cur.next.next;
        return pre.next;
    }
    int getLen(ListNode head)
    {
        int len=0;
        while(head!=null)
        {
            len++;
            head = head.next;
        }
        return len;
    }
}

20. 有效的括号

代码思路:

  • 像这种从里向外处理判断的题目,可以使用栈[后进先出]
  • 栈中保存的是下一个期待遇到的值
    • 也就是栈只保存右括号
  • 当遇到左括号
    • 期待下一个遇到的是相应的右括号,将相应的右括号压入栈中
  • 当遇到右括号
    • 期待栈中有对应的值
      • 当栈为空或者不是当前相应的右括号,都是非法的
class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for(char c : s.toCharArray())
        {
            if(c=='('){
                stack.add(')');
            }else if(c=='[')
            {
                stack.add(']');
            }else if(c=='{')
            {
                stack.add('}');
            }else{
                // System.out.println(stack);
                if(stack.isEmpty() || c!=stack.pop()) return false;
            }
        }
        return stack.isEmpty();
    }
}

21. 合并两个有序链表

代码思路

  • 每次选择较小的节点,接入结果链表head的尾部tail
  • 当L1或者L2为空时,break
  • 将剩余的链表接入tail
  • 返回head
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode head = new ListNode();
        ListNode cur = head;
        while(list1!=null && list2!=null)
        {
            if(list1.val<=list2.val)
            {
                cur.next = list1;
                list1 = list1.next;
            }else{
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        cur.next = list1==null?list2:list1;
        return head.next;
    }
}

22. 括号生成

括号生成,递归实现

  • 当前状态需要保证:左括号一定是大于或等于右括号的
  • 所以,在选择策略上,当左括号剩余数量大于零,选择左括号
  • 只有当左括号数量小于右括号时,才选择右括号
    • 当括号剩余总数等于0时,跳出循环
class Solution {
    List<String> res = new LinkedList<>();
    StringBuilder path = new StringBuilder();
    public List<String> generateParenthesis(int n) {
        dsf(n,n);
        return res;
    }
    void dsf(int l,int r)
    {
        if(l+r==0)
        {
            res.add(path.toString());
            return;
        }
        if(l>0)
        {
            path.append("(");
            dsf(l-1,r);
            path.setLength(path.length()-1);
        }
        if(l<r){
            path.append(")");
            dsf(l,r-1);
            path.setLength(path.length()-1);
        }
            
    }
}

31. 下一个排列

代码思路:

  • 贪心
    • 当一个数是最大数时,元素呈现降序,不存在前一个数大于后一个数【如:4321】
    • 当一个数不是最大值时,存在nums[i]
    • 从后往前,寻找大于最小影响点的值,与之交换,得到的数一定比原来的数值大【保证了较大要求】
      • 为了保证最小,影响点后面的元素保证最小,需要进行排列【保证较大中最小的要求】
class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length;
        if(n<=1) return;
        int l = -1;
        for(int i=n-2;i>=0;i--)
        {
            if(nums[i]<nums[i+1])
            {
                l = i;
                break;
            }
        }
        if(l==-1)
        {
            Arrays.sort(nums);
            return;
        }
        for(int i=n-1;i>l;i--)
        {
            if(nums[l]<nums[i])
            {
                int temp = nums[i];
                nums[i] = nums[l];
                nums[l] = temp;
                Arrays.sort(nums,l+1,n);
                return;
            }
        }
    }
}

32. 最长有效括号

代码思路:

  • 使用栈来存储合法括号的下标
    • 规定左括号为合法括号
    • 右括号消耗合法括号,同时计算右括号与上一个合法括号的距离,作为该右括号能够构成的最长合法距离
      • 当右括号消耗完后,没有对应的前一个合法下标[stack为空],那么合法下标最左,更新为当前右括号下标
  • 初始化
    • 合法括号下标-1,假设第一个元素合法,那么合法元素的前一个合法下标为-1
  • 含义
    • 保存最左合法下标
class Solution {
    public int longestValidParentheses(String s) {
        Stack<Integer> stack = new Stack<>();
        stack.add(-1);
        int max=0;
        for(int i=0;i<s.length();i++)
        {
            char cur = s.charAt(i);
            if(cur=='(')
            {
                stack.add(i);
            }else{
                Integer pre = stack.pop();
                if(stack.isEmpty()) stack.add(i);
                else
                max = Math.max(max,i-stack.peek());
            }
        }
        return max;
    }
}

33. 搜索旋转排序数组

代码思路:

  • 通过旋转的数组,被分为俩段递增子数组
    • 极端情况,就是一段递增数组
  • 主要思路:二分查找
    • 二分查找核心
      • 有序
      • 双指针不断查找范围【重点】
    • 如何不断缩小范围
      • 判断中点mid是否在递增子数组中
        • 若nums[0]<=nums[mid] 证明[0,mid]递增
      • 若target==nums[mid] 返回
      • 若nums[0]<=nums[mid]成立,判断中点值是否在递增子数组[0,mid)
        • 若在,则移动r=mid-1
        • 若不在,则移动l=mid+1
      • 若nums[0]<=nums[mid]不成立成立,判断mid是否在递增子数组[mid,n-1]中
        • 若在,则移动l=mid+1
        • 若不在,则移动r=mid-1
class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        int l=0,r=n-1;
        while(l<=r)
        {
            int mid = l+(r-l)/2;
            if(nums[mid]==target)
            {
                return mid;
            }
            if(nums[0]<=nums[mid])
            {
                if(nums[0]<=target && target<nums[mid])
                {
                    r = mid-1;
                }else{
                    l = mid+1;
                }
            }else{
                if(nums[mid]<target && target<=nums[n-1])
                {
                    l = mid+1;
                }else r = mid-1;
            }
        }
        return -1;
    }
}

34. 在排序数组中查找元素的第一个和最后一个位置

代码思路

  • 通过二分法,先找到最右目标下标,然后重新二分寻找最左下标
  • 红蓝二分法可以解决
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int l=-1;
        int r = nums.length;
        while(l+1!=r){
            int mid = l+(r-l)/2;
            if(nums[mid]<target){
                l = mid;
            }else r = mid;
        }
        if(r==nums.length || nums[r]!=target) return new int[]{-1,-1};
        int L = r;
        l = L-1;
        r = nums.length;
        while(l+1!=r){
            int mid = l+(r-l)/2;
            if(nums[mid]==target){
                l = mid;
            }else r = mid;
        }
        int R = l;
        return new int[]{L,R};
    }
}

39. 组合总和

代码思路

  • 深搜DFS,
    • 元素可以无限次使用,所以可以对数组进行排序,每次取当前最小index,直到target为0或者小于零时,为出口
    • 树层:当前最小元素开始为树层
    • 树高:最低为当前取最小值,直到target小于等于零
    • 出口:target小于等于零
class Solution {
    List<List<Integer>> res = new LinkedList<>();
    Deque<Integer> path = new LinkedList<>();
    int[] candidates;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        this.candidates = candidates;
        dsf(0,target);
        return res;
    }
    void dsf(int index,int target)
    {
        if(target==0)
        {
            res.add(new LinkedList<>(path));
            return;
        }
        if(target<0) return;
        for(int i=index;i<candidates.length && target>=candidates[i];i++)
        {
            path.addLast(candidates[i]);
            dsf(i,target-candidates[i]);
            path.removeLast();
        }
    }
}

42. 接雨水

代码思路:

  • 单调栈stack:如何考虑单调栈问题
    • 递增还是递减?换句话说,什么时候需要进行处理,什么时候不需要进行处理?处理结果是什么?
  • 接雨水属于单调递减栈,意味着当bigger element coming need deal
  • 为何要选单调递减?
    • 这是由于,当单调递减时,并不积雨,就是没有对结果造成影响
    • 当单调递减时,突然遇到一个比最小值(栈顶)大的元素,此时会形成凹槽,只需要收集凹槽里的雨水即可
      • 这里有一个误区,认为凹槽是一次性算出来的,其实凹槽是一步一步累和的
    • 如何计算凹槽?
      • 但遇到较大元素B,此时栈顶元素Low的高度一定小于栈顶第二元素Pre,同时小于B (Pre>Low && Low
      • 我们每次计算的是,此时由栈顶Low元素为“托盘”所能积攒的雨水
        • 当前雨水 = w*h = (B-Pre-1) * (Min{[B],[Pre]}-[Low]): 单调栈存储的是数组下标
    • 当stack为空,pre不存在时?
      • 那证明本次处理,没有左柱子,将较大元素B压入栈中,作为左柱子参考
class Solution {
    public int trap(int[] height) {
        Stack<Integer> stack = new Stack<>();
        int n = height.length;
        int res = 0;
        for(int i=0;i<n;i++)
        {
            if(stack.isEmpty())
            {
                stack.add(i);
                continue;
            }
            int num = height[i];
            while(!stack.isEmpty() && num>height[stack.peek()])
            {
                int mid = stack.pop();
                if(stack.isEmpty()) continue;
                int pre = stack.peek();
                int w = i-pre-1;
                int h = Math.min(num,height[pre]) - height[mid];
                res += h*w;
            }
            stack.add(i);
        }
        return res;
    }
}

46. 全排列

代码思路

  • 全排列问题作为经典的DFS题目,有两个特点
    • 无序,且不重复使用
    • 没有重复元素
  • 根据这两个特点
    • 无序:需要使用used[] 来标记当前树层中的元素哪一些时处理过的
    • 没有重复元素:不用考虑排序后的去重问题
class Solution {
    List<List<Integer>> res = new LinkedList<>();
    Deque<Integer> path = new LinkedList<>();
    int[] nums;
    public List<List<Integer>> permute(int[] nums) {
        int n = nums.length;
        this.nums = nums;
        boolean[] used = new boolean[n];
        dsf(used);
        return res;
    }
    void dsf(boolean[] used)
    {
        if(path.size()==nums.length)
        {
            res.add(new LinkedList(path));
            return;
        }
        for(int i=0;i<nums.length;i++)
        {
            if(!used[i])
            {
                used[i] = true;
                path.add(nums[i]);
                dsf(used);
                path.removeLast();
                used[i] = false;
            }
        }
    }
}

48. 旋转图像

代码思路

  • 数组移动问题

    • 数组拷贝 O(N)
    • 原地反转,找规律
  • 题目顺时针旋转90度后,呈现规律

    • (当n=4时)
      • (0,0)->(0,3)
      • (0,1)->(1,3)
      • (0,2)->(2,3)
      • (1,1)->(1,2)
      • (i,j)->(j,n-j-1)
  • 顺时针顺序

  • [i][j] --> [j][n-i-1] --> [n-i-1][n-j-1] -->[n-j-1][i]
    
  • 当顺时针旋转后

  • [i][j]被赋值的是最后一个[n-j-1][i]
    
  • 旋转个数

    • 当N为偶数,行和列都遍历N/2个元素
    • 当N为奇数,行+1==列,这是(N/2+1)+N/2 == N,此时选择行大1,旋转后刚好等于N/2–>N
class Solution {
    public void rotate(int[][] matrix) {
        // [i][j] -> [n-j-1][i] [0][1] - > [2][0]
        int n = matrix.length;
        for(int i=0;i<(n+1)/2;i++)
        {
            for(int j=0;j<n/2;j++)
            {
                int t = matrix[i][j];
                matrix[i][j] = matrix[n-j-1][i];
                matrix[n-j-1][i] = matrix[n-i-1][n-j-1];
                matrix[n-i-1][n-j-1] = matrix[j][n-i-1];
                matrix[j][n-i-1] = t;
            }
        }
    }
}

49. 字母异位词分组

代码思路

  • 利用Map进行分组
  • 分组条件:将str转换为字典序最小的字符串strMin,保证同类字母异位词,strMin唯一
  • 以strMin为key,put到Map中,遍历map.values即可
class Solution {
    List<List<String>> res = new LinkedList<>();
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String,List<String>> dic = new HashMap<>();
        for(String str : strs)
        {
            String cur =  getCode(str);
            
            if(!dic.containsKey(cur)) dic.put(cur,new ArrayList<>());
            dic.get(cur).add(str);
        }
        res.addAll(dic.values());
        return res;
    }
    String getCode(String str)
    {
        int[] arr = new int[26];
        StringBuilder res = new StringBuilder();
        for(char c : str.toCharArray()) arr[c-'a']++;
        for(int i=0;i<arr.length;i++)
        {
            if(arr[i]==0) continue;
            String cur = ((char)('a'+i))+"";
            res.append(cur.repeat(arr[i]));
        }
        return res.toString();
    }
}

53. 最大子数组和

代码思路

  • 动态规划dp[i] = (nums[i],dp[i-1]+nums[i-1]),其中dp[i]表示必须以第i个元素结尾最大的连续子数组和
  • 但是,bp[i]的状态只有bp[i-1]和nums[i-1]有关,可以采用递推的发生维护最大值
    • 遍历元素ele,利用pre,ele,不断更新最大值
    • pre表示前子元素最大子数组和
class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        if(n==0) return 0;
        int max = nums[0];
        int pre = nums[0];
        for(int i=1;i<n;i++)
        {
            max = Math.max(max,Math.max(nums[i],nums[i]+pre));
            pre = Math.max(nums[i],nums[i]+pre);
        }
        return max;
    }
}

55. 跳跃游戏

贪心:

  • 最大值表示:当前可以达到的最远距离
  • 初始化: max = 0 + nums[0],表示当前从0开始最大能走到max下标位置
  • 通过能到达的点,不断更新max
  • 当max>=n-1时,证明可以走到终点
class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length;
        if(n<=1) return true;
        int max=nums[0];
        for(int i=1;i<n;i++)
        {
            if(i<=max)
            {
                max = Math.max(max,i+nums[i]);
                if(max>=n-1) return true;
            }
        }
        return max>=n-1;
    }
}

56. 合并区间

双指针:

  • l,r表示的是,当前可能的区间长度
  • 如何确定当前区间[l,r]是独立?
    • 对区间进行排序 --> 为了能够确定当前区间[l,r]是否是独立的
    • 当最小起点L,延申出去R,都达不到比L稍小的排序后的下一区间next的起点next[0],那么证明当前l,r是独立的,脱节的。[l,r]为答案的一个独立区间,更新l,r从下一个起点开始合并,l = next[0],r=next[1]
    • 当最小起点L,延申出去R,达到了next[0],证明当前俩个区间是交融在一起的,更新l,r,r=Max{r,next[1]} (其实l不用更新)
class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals,(arr,brr)->arr[0]-brr[0]);
        List<int[]> res = new ArrayList<>();
        int n = intervals.length;
        if(n<=1) return intervals;
        int l=intervals[0][0],r = intervals[0][1];
        for(int i=1;i<n;i++)
        {
            int[] next = intervals[i];
            if(r<next[0])
            {
                res.add(new int[]{l,r});
                l = next[0];
                r = next[1];
            }else{
                r = Math.max(r,next[1]);
            }
        }
        res.add(new int[]{l,r});
        return res.toArray(new int[0][]);
    }
}

62. 不同路径

动态规划:

状态转移:bp[i][j]=bp[i-1][j-1]; // i,j表示第i行j列,并且从一开始
这是因为机器人只能向下或者向右移动,只由两个状态决定-->爬楼梯问题;
初始化:bp[i][j]=1;bp[0][j]=0;bp[i][0]=0;
class Solution {
    public int uniquePaths(int m, int n) {
        int[][] bp = new int[m+1][n+1];
        bp[1][1] = 1;
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(i==1 && j==1) continue;
                bp[i][j] = bp[i-1][j] + bp[i][j-1];
            }
        }
        return bp[m][n];
    }
}

64. 最小路径和

动态规划

1. 因为只能向下或者向右移动,只由两个状态决定-->爬楼梯问题;
2. 状态转移方程 bp[i][j] = Max{bp[i-1][j],bp[i][j-1]} + nums[i-1][j-1];
3. 初始化 bp[1][1] = nums[0][0]
class Solution {
    int n,m;
    public int minPathSum(int[][] grid) {
        n = grid.length;
        m = grid[0].length;
        int[][] bp = new int[n+1][m+1];
        for(int[] arr : bp) Arrays.fill(arr,100*200);
        bp[1][1] = grid[0][0];
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(i==1 && j==1) continue;
                bp[i][j] = Math.min(bp[i-1][j],bp[i][j-1])+grid[i-1][j-1];
            }
        }
        return bp[n][m];
    }
}

70. 爬楼梯

爬楼梯问题

class Solution {
    public int climbStairs(int n) {
        if(n<3) return n;
        int[] bp = new int[n];
        bp[0] = 1;
        bp[1] = 2;
        for (int i = 2; i < n; i++) {
            bp[i] = bp[i-1]+bp[i-2];
        }
        return bp[n-1];
    }
}

72. 编辑距离

动态规划

  • 如何考虑状态?

    • 两个字符串一般bp[i][j]: i表示前一个字符串前i位,j表示后一个字符串前j位,bp[i][j]表示将s[0,i]-->p[0,j]编辑距离最短值,i表示的是字符串的元素个数也可以理解为从零开始的长度,不是下标
      
  • 如何考虑状态转移?

    • 当[i]==[j] bp[i][j] = bp[i-1][j-1]
      当[i]!=[j] bp[i][j] = min{bp[i-1][j],bp[i-1][j-1],bp[i][j-1]}+1
      	1. word1选择删除 --> bp[i-1][j]
      	2. word1选择替换(一定替换成与[j]相同的最小) --> bp[i-1][j-1]
      	3. word1选择插入(一定插入成与[j]相同的最小) --> bp[i][j-1]
      	4. 每个操作对应一次操作,结果+1
      
  • 初始化

    • bp[0][j] = j;bp[i][0]=i;//对应删除与插入
      
class Solution {
    public int minDistance(String word1, String word2) {
        int n = word1.length(),m = word2.length();
        int[][] bp = new int[n+1][m+1];
        for(int i=0;i<=n;i++) bp[i][0] = i;
        for(int i=0;i<=m;i++) bp[0][i] = i;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                char cur1 = word1.charAt(i-1);
                char cur2 = word2.charAt(j-1);
                if(cur1==cur2)
                {
                    bp[i][j] = bp[i-1][j-1];
                }else{
                    int remove = Math.min(bp[i-1][j],bp[i][j-1])+1;
                    int replace = bp[i-1][j-1]+1;
                    int insert = Math.min(bp[i][j-1],bp[i-1][j])+1;
                    bp[i][j] = Math.min(remove,Math.min(replace,insert));
                }
            }
        }
        return bp[n][m];
    }
}

75. 颜色分类

快排:直接排序

class Solution {
    int[] nums;
    public void sortColors(int[] nums) {
        this.nums = nums;
        partition(0,nums.length-1);
    }
    void partition(int lo,int ro)
    {
        if(lo>ro) return;
        int l=lo,r=ro,divi=nums[lo];
        while(true)
        {
            while(l<=r && nums[l]<=divi) l++;
            while(l<=r && nums[r]>divi) r--;
            if(l>r) break;
            swap(l,r);
        }
        swap(lo,r);
        partition(lo,r-1);
        partition(r+1,ro);
    }
    void swap(int l,int r)
    {
        int t = nums[l];
        nums[l] = nums[r];
        nums[r] = t;
    }
}

78. 子集

代码思路:

  • 子集,将所有路径都保存
  • 树层:当前元素以及后续元素
  • 树高:当当前元素下标index大于等于数组长度,跳出循环
class Solution {
    List<List<Integer>> res = new LinkedList<>();
    Deque<Integer> deque = new LinkedList<>();
    int[] nums;
    public List<List<Integer>> subsets(int[] nums) {
        this.nums = nums;
        dsf(nums.length,0);
        return res;
    }
    void dsf(int n,int index)
    {
        res.add(new LinkedList(deque));
        if(index>=n)
        {
            return;
        }
        for(int i = index;i<n;i++)
        {
            deque.add(nums[i]);
            dsf(n,i+1);
            deque.removeLast();
        }
    }
}

79. 单词搜索

深搜DFS:从某一点出发,累计当前匹配个数N

树层:上下左右四个方向,且当前未被访问过的方向

出口:当当前匹配个数N==str.length()

class Solution {
    char[][] board;
    String word;
    int n,m,len;
    public boolean exist(char[][] board, String word) {
        this.board = board;
        this.word = word;
        this.n=board.length;
        this.m = board[0].length;
        boolean[][] used = new boolean[n][m];
        this.len = word.length();
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(dsf(i,j,0,used)) return true;
            }
        }
        return false;
    }
    boolean dsf(int x,int y,int cnt,boolean[][] used)
    {
        if(cnt==len)
        {
            return true;
        }
        if(x<0 || y<0 || x>=n || y>=m) return false;
        if(board[x][y]!=word.charAt(cnt) || used[x][y]) return false;
        used[x][y] = true;
        boolean res = dsf(x,y+1,cnt+1,used) || dsf(x+1,y,cnt+1,used) || dsf(x,y-1,cnt+1,used) || dsf(x-1,y,cnt+1,used);
        used[x][y] = false;
        return res;

    }
}

84. 柱状图中最大的矩形

单调栈:

  • 与接雨水类似,但是最大矩形是当最小值出现时,处理stack
  • 且为了方便,前后添加两个0高度的虚拟柱子
    • 首个0,计算时,不会出现stack为空的情况
    • 尾巴0,为了在结束的时候,自动计算stack中的矩形面积
  • 计算公式
    • h = [stack.pop()];
    • w = (i-stack.peek()-1)
class Solution {
    public int largestRectangleArea(int[] heights) {
        // coming small deal
        int len=heights.length,res = 0;
        if(len==0) return 0;
        int[] copy = new int[len+2];
        System.arraycopy(heights,0,copy,1,len);
        heights = copy;
        len+=2;
        Stack<Integer> stack = new Stack<>();
        stack.add(0);
        for (int i = 1; i < len; i++) {
            while(!stack.isEmpty() && heights[stack.peek()]>heights[i])
            {
                int h = heights[stack.pop()];
                int w = i-stack.peek()-1;
                res = Math.max(res,h*w);
            }
            stack.add(i);
        }
        return res;
    }
}

85. 最大矩形

代码思路:

  • 从列上看,累和当前元素高度

    • if(g[i][j]=='1') bp[i][j] = bp[i-1][j]+1;
      
  • 累和过程中,从右到左,计算该结点往左边[k,0],能构成的矩形的最大值

    维护一个minH,表示当前矩形中最小的高度,若minH>0,则找到一个可行的矩形;
    int curS[k,j] = (j-k+1)*minH
    
class Solution {
    int n,m;
    public int maximalRectangle(char[][] matrix) {
        this.n = matrix.length;
        this.m = matrix[0].length;
        int[][] bp = new int[n][m];
        int max=0;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(matrix[i][j]=='1')
                {
                    bp[i][j] = 1;
                    if(i-1>=0) bp[i][j] += bp[i-1][j];
                    int minH = bp[i][j];
                    for(int k=j;(k>=0 && matrix[i][k]=='1');k--)
                    {
                        minH = Math.min(minH,bp[i][k]);
                        max = Math.max(max,(j-k+1)*minH);
                    }

                }
            }
        }
        return max;
    }
}

94. 二叉树的中序遍历

考察二叉树的中序遍历

class Solution {
    List<Integer> res = new ArrayList<>();
    public List<Integer> inorderTraversal(TreeNode root) {
        if(root==null) return res;
        inorderTraversal(root.left);
        res.add(root.val);// 中序
        inorderTraversal(root.right);
        return res;
    }
}

101. 对称二叉树

代码思路

  • 一个树,如果呈对称性质
    • 那么对称节点A,B相同
      • 对称节点的A左节点与B右节点对称
      • 对称节点的A右节点与B左节点对称
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root==null || (root.left==null && root.right==null)) return true;
        return reverse(root,root);
    }
    public boolean reverse(TreeNode left,TreeNode right)
    {
        if(left==null && right==null) return true;
        if(left==null || right==null) return false;
        if(left.val!=right.val) return false;
        return reverse(left.left,right.right) && reverse(left.right,right.left);
    }
}

104. 二叉树的最大深度

代码思路

  • 一个树的节点,如果不是叶子节点,那它的深度H=Max{左子树深度,右子树深度}+1
class Solution {
    public int maxDepth(TreeNode root) {
        if(root==null) return 0;
        if(root.left==null && root.right==null) return 1;
        return 1 + Math.max(maxDepth(root.left),maxDepth(root.right));
    }
}

代码思路

  • 也可以采用DFS深搜的方式,记录叶子节点的深度,取最大
class Solution {
    int max;
    public int maxDepth(TreeNode root) {
        dsf(root,0);
        return max;
    }
    void dsf(TreeNode root,int h){
        if(root==null){
            max = Math.max(max,h);
            return;
        }
        dsf(root.left,h+1);
        dsf(root.right,h+1);
    }
}

102. 二叉树的层序遍历

树的层序遍历,一般借助队列完成

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    Deque<TreeNode> queue = new LinkedList<>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root==null) return res;
        queue.add(root);
        while(!queue.isEmpty())
        {
            int size = queue.size();
            ArrayList<Integer> path = new ArrayList<>();
            for(int i=0;i<size;i++)
            {
                TreeNode top = queue.pop();
                path.add(top.val);
                if(top.left!=null) queue.add(top.left);
                if(top.right!=null) queue.add(top.right);
            }
            res.add(path);
        }
        return res;
    }
}

121. 买卖股票的最佳时机

贪心:

  • 买卖股票,且只买卖一次,那么一定是在低点买入,高点卖出时获取到最大利润
    • 不断记录当前股市的最小买入点
      • 每次尝试卖出,更新最大利润
      • 同时,更新最小值
    • 从几何上看,其实求解的是每个点,往前看(这是因为买入时一定比卖出时早),所能构成的最大正数差值
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        if(n<=1) return 0;
        int min = prices[0];
        int ans = 0;
        for(int num : prices)
        {
            ans = Math.max(num-min,ans);
            min = Math.min(min,num);
        }
        return ans;
    }
}

128. 最长连续序列

代码思路

  • 在无序重复的数组中,找到等差数列公差等于一的序列,可任意排序,要求不重复
    • 不重复,可用set来去重,同时由set获取元素复杂度为O(1)
    • 公差等于一,如何确定策列
      • 遍历set,获取到当前元素num,公差为1,那么next=num+1
      • 判断next是否在set中
        • 如果在set中,那么num++,继续判断下一个next
        • 直到next不在set中,记录循环次数即可获取以num为起点的等差队列的长度
    • 时间复杂度
      • O(n^2),每个元素都要循环一遍,超时
    • 优化
      • 不需要,等差数列上的每个值都作为起点,遍历循环
      • 判断当该点是等差数列起点,才进行循环,也就是当num-1不存在set中
    • 时间复杂度(优化)
      • O(N)
class Solution {
    public int longestConsecutive(int[] nums) {
        if(nums.length<=1) return nums.length;
        Set<Integer> set = new HashSet<>();
        for(int num : nums)
        {
            set.add(num);
        }
        int max=1;
        for(int num : set)
        {
            int findLen=1;
            if(!set.contains(num - 1)) 
            {
                while(set.contains(num+1))
                {
                    findLen++;
                    num++;
                }
                max = max>findLen?max:findLen;
            }
            
            
        }
        return max;
    }
}

136. 只出现一次的数字

代码思路

  • 统计元素出现次数,暴力hashMap,时间复杂度O(2*N)
  • 一次遍历能否搞定?
    • 异或操作 (^)
      • 特点:两数相同异或为0,任何数与0异或不变
    • 初始化为0,不改变结果
class Solution {
    public int singleNumber(int[] nums) {
        int ans=0;
        for(int num : nums)
        {
            ans ^= num;
        }
        return ans;
    }
}

139. 单词拆分

动态规划:

  • 动态规划,解决字符串问题,要么bp[i]表示从0开始到i,要么**bp[i][j]**表示从i到j的结果
  • 本题只需要解决整个字符串能否被表示,即可,bp[i]表示: 从第0个字符开始,到第i个字符结束的字符串能否被word表示
  • bp[i] = bp[j] && word.contains(str.substring(j,i)); 0<=j<=i
    • j==i时,表示bp[i]能否被word组成
    • j
    • 其中bp[j]是集合的体现,看则bp[i]被分为两段bp[j]和str[j::i]
      • 实则bp[j]表示的是str[0::j]被切分为多块,能否被word表示
class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        int n = s.length();
        boolean[] bp = new boolean[n+1];
        bp[0] = true;
        for(int i=1;i<=n;i++)
        {
            for(int j=i;j>=0;j--)
            {
                bp[i] = bp[j] && wordDict.contains(s.substring(j,i));
                if(bp[i]==true) break;
            }
        }
        return bp[n];
    }
}

141. 环形链表

快慢指针的经典题目

  • 如果存在环,那么可以指针是走不出来的,也就是进入了死循环,路程为无穷
    • 如果指针到达了终点,那么不存在环
  • 如果存在环,两个指针A、B,A一次走一格,B一次走两格,每次B与A的差值都为减一(1列表的距离单元),存在环路程无穷,当A迟早会被B追上
    • 如果没有环,B根本不会走回头路,没有相遇的可能
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null){
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (fast.next!=null && fast.next.next!=null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast==slow) return true;
        }
        return false;
    }
}

142. 环形链表 II

代码思路

  • 找环入口的算法
    • 第一次相遇,slow = nb
    • a+nb = 入口点
      • slow再走a = 入口
      • head走到入口 = a
    • 起始距离入口 = 第一次相遇位置 + a
  • 俩次循环
    • 第一次找到环,也就是快慢指针相碰,此时,slow指针距离入口为a路程,head指针距离入口为a路程
    • 第二次循环,head与slow同时向前走,直到相碰,此时都走了a,且为入口
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head, slow = head;
        while (true) {
            if (fast == null || fast.next == null) return null;
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) break;
        }
        fast = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return fast;
    }
}

代码思路

  • 用Set来存储slow指针是否往回走,往回走的点为入口
public class Solution {
    ListNode fast,slow;
    public ListNode detectCycle(ListNode head) {
        fast = head;
        slow = head;
        int pos=0;
        Set<ListNode> set = new HashSet<>(10000);
        while(fast!=null && fast.next!=null)
        {
            if(set.contains(slow)) return slow;
            if(!set.contains(slow)) set.add(slow);
            fast = fast.next.next;
            slow = slow.next;
            pos++;
        }
        return null;
    }
}

146. LRU 缓存

考点:双向链表 + HashMap

  • 使用双向链表来存储缓存数据(key,value)
  • hashMap用于定位元素(key,value),加快数据更新
class LRUCache {
    // 数据存储
    Node head,tail;
    // 管理定位数据[key::value]
    Map<Integer,Node> dic;
    // 容量
    int capacity;
    int size;
    public LRUCache(int capacity) {
        this.head = new Node();
        this.tail = new Node();
        head.next = tail;
        tail.pre = head;
        dic = new HashMap<>(capacity+10);
        this.capacity = capacity;
    }
    
    public int get(int key) {
        if(!dic.containsKey(key)) return -1;
        Node node = dic.get(key);
        moveToHead(node);
        return node.value;
    }
    void moveToHead(Node node)
    {
        removeNode(node);
        addToHead(node);
    }
    void removeNode(Node node)
    {
        if(node==null || !dic.containsKey(node.key)) return;
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }
    void addToHead(Node node)
    {
        node.pre = head;
        node.next = head.next;
        head.next = node;
        node.next.pre = node;
    }
    
    public void put(int key, int value) {
        Node node = dic.get(key);
        if(node==null)
        {
            node = new Node(key,value);
            size++;
        }
        // 更新
        node.value = value;
        // 最新数据
        moveToHead(node);
        dic.put(key,node);
        if(size>capacity)
        {
            Node last = removeLast();
            dic.remove(last.key);
            size--;
        }
    }
    Node removeLast()
    {
        Node last = tail.pre;
        removeNode(last);
        return last;
    }
    class Node {
        int capacity;
        int size;
        int key;
        int value;
        Node pre;
        Node next;
        public Node(){}
        public Node(int key,int value)
        {
            this.key = key;
            this.value = value;
        }
    }
}

148. 排序链表

归并排序

  • 排序出口:当链表长度<=1时,return;
  • 使用快慢指针,将链表切割成两段(mid.next = null),分别排序,排序后合并链表
class Solution {
    public ListNode sortList(ListNode head) {
        if(head==null || head.next==null) return head;
        // 记得,fast=head.next,向下取整(奇数个节点找到中点,偶数个节点找到中心左边的节点)
        ListNode mid = getMid(head);
        ListNode head2 = mid.next;
        // 将链表分段
        mid.next = null;
        ListNode left = sortList(head);
        ListNode right = sortList(head2);
        return merge(left,right);
    }
    ListNode getMid(ListNode head)
    {
        ListNode slow=head,fast=head.next;
        while(fast!=null && fast.next!=null)
        {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
    public ListNode merge(ListNode head1, ListNode head2) {
        ListNode left = head1,right=head2;
        ListNode temp = new ListNode(0);
        ListNode cur = temp;
        while(left!=null && right!=null)
        {
            if(left.val<=right.val)
            {
                cur.next = new ListNode(left.val);
                left = left.next;
            }else
            {
                cur.next = new ListNode(right.val);
                right = right.next;
            }
            cur = cur.next;
        }
        cur.next = left==null?right:left;
        return temp.next;
    }
}

152. 乘积最大子数组

代码思路

  • 数组存在负数,最小值乘积可能遇到负数摇身一变变成最大值,最大值也可能乘积后变成最小值
  • 维护两个乘积dp,分别记录当前状态可能的最大值和最小值
    • dmax[i] = Math.max(nums[i],Math.max(nums[i]*dmax[i-1],nums[i]*dmin[i-1]));
    • dmin[i] = Math.min(nums[i],Math.min(nums[i]*dmax[i-1],nums[i]*dmin[i-1]));
class Solution {
    int max;
    public int maxProduct(int[] nums) {
        int n = nums.length;
        int[] dmax = new int[n];
        int[] dmin = new int[n];
        dmax[0] = nums[0];
        dmin[0] = nums[0];
        max = nums[0];
        for(int i=1;i<n;i++)
        {
            dmax[i] = Math.max(nums[i],Math.max(nums[i]*dmax[i-1],nums[i]*dmin[i-1]));
            dmin[i] = Math.min(nums[i],Math.min(nums[i]*dmax[i-1],nums[i]*dmin[i-1]));
            max = Math.max(max,Math.max(dmin[i],dmax[i]));
        }
        return max;
    }
}

155. 最小栈

代码思路:

  • 要保存最小值信息,除了维护一个正常工作的栈Daily,还要一个单调递减(不严格)的单调栈Min保留最小值
  • 具体操作
    • 当一个元素入栈时
      • 如果不大于Min的栈顶元素,压入Min
    • 为什么较大元素MAX,Min不处理
      • 这是由于栈是一个先进后出的结构,Min维护的是当前最小值。晚来的MAX,如果较早来的元素MIN比MAX小,那么在MIN存在的时候,MAX不可能成为最小值,而MAX又比MIN先出栈,所有MAX是被丢弃是数据,min不进行保留
class MinStack {
    Stack<Integer> daily;
    Stack<Integer> min;
    public MinStack() {
        this.daily = new Stack<>();
        this.min = new Stack<>();
    }
    
    public void push(int val) {
        daily.add(val);
        if(min.isEmpty() || min.peek()>=val)
        min.add(val);
    }
    
    public void pop() {
        if(daily.isEmpty()) return;
        int top = daily.pop();
        if(top==min.peek()) min.pop();
    }
    
    public int top() {
        if(daily.isEmpty()) return -1;
        return daily.peek();
    }
    
    public int getMin() {
        if(min.isEmpty()) {
            return -1;
        }
        return min.peek();
    }
}

160. 相交链表

代码思路

  • 两条链表的长度分别为a,b
    • A链表走完A链表后走B链表,一共走了a+b
    • B链表走完B链表后走A链表,一共走了a+b
    • 同样的速度,走完同样的路程
      • 如果有相交的路段,一定会相遇
      • 如果没有相交的路段,那么同样的状态,A,B都会走到终点null,返回null
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null && headB==null) return null;
        if(headA==null || headB==null) return null;
        ListNode ha = headA,hb = headB;
        while(ha!=hb)
        {
            ha = ha==null?headB:ha.next;
            hb = hb==null?headA:hb.next;
        }
        return ha;
    }
}

169. 多数元素

代码思路

  • 摩根投票
    • 多数元素的个数大于其他元素的个数,用多数元素的个数减去其它元素的个数,数值大于零
  • 具体操作
    • 选举一个元素ele
      • 如果元素出现,vo++
      • 如果其他元素出现,vo–
      • 如果vo==0,更换ele
class Solution {
    public int majorityElement(int[] nums) {
        int ele = nums[0];
        int vo=0;
        for(int num : nums)
        {
            if(vo==0)
            {
                ele = num;
            }
            vo+=num==ele?1:-1;
        }
        return ele;
    }
}

198. 打家劫舍

动态规划

  • 抢与不抢
    • bp[i]:抢劫[0,i]区间,能获取到的最大利润,i表示个数不是下标
    • bp[i] = Max{bp[i-1],bp[i-2]+nums[i]}
class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if(n<=1) return nums[0];
        int[] bp = new int[n+1];
        bp[1] = nums[0];
        for(int i=2;i<=n;i++)
        {
            bp[i] = Math.max(bp[i-1],bp[i-2]+nums[i-1]);
        }
        return bp[n];
    }
}

200. 岛屿数量

代码思路:

  • 遇到岛屿,感染岛屿成海洋,ans++
  • DSF感染岛屿即可
class Solution {
    char[][] grid;
    int res;
    public int numIslands(char[][] grid) {
        this.grid = grid;
        for(int i=0;i<grid.length;i++)
        {
            for(int j=0;j<grid[0].length;j++)
            {
                if(grid[i][j]=='1')
                {
                    dsf(i,j);
                    res++;
                }
            }
        }
        return res;
    }
    void dsf(int x,int y)
    {
        if(x<0 || y<0 || x>=grid.length || y>=grid[0].length || grid[x][y]!='1')
        {
            return;
        }
        grid[x][y] = '0';
        dsf(x,y+1);
        dsf(x+1,y);
        dsf(x,y-1);
        dsf(x-1,y);
    }
}

206. 反转链表

递归反转:

  • 反转下一个链表节点,处理前一个链表与反转后的链表的关系
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head==null || head.next==null) return head;
        ListNode temp = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return temp;
    }
}

394. 字符串解码

代码思路

  • 外层数据依赖里面数据展开得到的

    • 例如 ab2[ef]–> 需要由ef展开后得到 efef并与ab拼接后,才能得到答案
    • 那么在计算ef时需要提前记录好的数据就需要 一个数字表示repeat [ef] 多少次,并且还需要知道ef前面的数据ab
  • 如何保留[ef]前边数据,以便于在计算ef时可以用到呢?

    • 一般化表达式 ab2[ef]–>.[…[ab2[ef]]…].,我们知道ab2[ef]有可能并不是最外层,那么我们需要在进入[ef]时,就记录前一个[到[ef的数据,也就是res,mul保留的其实是前一个[到[的数字和字符串信息
  • 如果不好理解为什么res=new StringBuilder(strs.pop().toString() + res.toString().repeat(nums.pop()));

    • 可以将每一个字符串看成 3[a]2[bc]a–>1[3[a]2[bc]a]
    • 那么当[3**[**–>自然res压入的是""
    • 1[3[a**]2[b: 展开是res = aaa, 因为res指的是1[3[a]2[**bc]a] 这两个现阶段的字符串信息(加粗表示)
  • 为什么使用栈记录res,mul?

    • 从里到外,恢复字符串时,最里层用到的时次里层,次里层又是较晚出现的,满足后进先出的规律–>用栈来表达更好
class Solution {
    public String decodeString(String s) {
        Stack<Integer> nums = new Stack<>();
        Stack<StringBuilder> strs = new Stack<>();
        StringBuilder res = new StringBuilder();
        int mul = 0;
        for(char c : s.toCharArray())
        {
            if(Character.isDigit(c))
            {
                mul = mul * 10 +(c-'0');
            }else if(c=='[') // 记录、清除数据
            {
                nums.add(mul);
                strs.add(new StringBuilder(res));
                res.setLength(0);
                mul = 0;
            }else if(c==']') // 展开数据
            {
                res = new StringBuilder(strs.pop().toString() + res.toString().repeat(nums.pop()));
            }else{
                res.append(c);
            }
        } 
        return res.toString();
    }
}

215. 数组中的第K个最大元素

  • 可以使用大根堆,弹出k个元素即可
  • 可以使用快排partition,partition作用返回元素在数组排序后正确的下标,那么第k大元素,相当于下标位n-k
    • 使用双指针根据partition的返回值,定位可能的区间,快速获取n-k的元素
class Solution {
    public int findKthLargest(int[] nums, int k) {
        // sorted in index=n-k
        int n = nums.length;
        int tarIndex = n-k;
        int l=0,r=n-1;
        while(l<=r)
        {
            int cur = partition(nums,l,r);
            if(cur==tarIndex){
                return nums[cur];
            }else if(cur>tarIndex)
            {
                r = cur-1;
            }else l = cur+1;
        }
        return -1;
    }
    public int partition(int[] nums,int lo,int ro)
    {
        if(lo>ro) return -1;
        int l = lo,r=ro,fir=nums[lo];
        while(true)
        {
            while(l<=r && nums[l]<=fir) l++;
            while(l<=r && nums[r]>fir) r--;
            if(l>r) break;
            swap(nums,l,r);
        }
        swap(nums,lo,r);
        return r;
    }
    public void swap(int[] nums,int l,int r)
    {
        int temp = nums[l];
        nums[l] = nums[r];
        nums[r] = temp;
    }
}

221. 最大正方形

动态规划

  • 首先一个点A要想构成正方形(指的是A向左边和上边扩展能达到的最大正方形),要由上边点B、左边点C、左上点D的点共同决定
  • 为什么?
    • 假设A想要构成一个边长为N的正方形
      • 上边B点需要向上补齐最少N-1
      • 左边C点需要向左补齐最少N-1
      • 左上角D点需要向左补齐最少N-1
    • 当某一个条件不满足,A就不能向左扩展出一个N的正方形
    • 同理,A能扩展的最大值,要BCD同时满足,那么BCD取最小值的时候,可以同时满足
      • Max{A} = Min{B,C,D}+1
class Solution {
    public int maximalSquare(char[][] matrix) {
        int n = matrix.length,m=matrix[0].length;
        int[][] bp = new int[n][m];
        int max = 0;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
               if(matrix[i][j]=='1')
               {
                   bp[i][j] = 1;
                   if(i-1>=0 && j-1>=0) bp[i][j] += Math.min(bp[i-1][j-1],Math.min(bp[i-1][j],bp[i][j-1]));
                   max = Math.max(max,bp[i][j]);
               }
            }
        }
        return max*max;
    }
}

226. 翻转二叉树

代码思路

  • 左右子树反转后,调整父节点
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if(root==null || (root.left==null && root.right==null)) 
            return root;
        TreeNode left = invertTree(root.left);
        TreeNode right = invertTree(root.right);
        root.left = right;
        root.right = left;
        return root;
    }
}

238. 除自身以外数组的乘积

动态规划:

  • 先求左边数组的乘积,保留在数组里: left[i] = left[i-1] * nums[i-1] (i指的是下标)
  • right也可以同理求得,当是right不需要记录,所以可以用迭代的方式处理
    • right初始化为1,表示第n-1层right的乘积
    • 逆序, 每次right*nums[i+1] (i从n-2开始)
    • 结果left[i]*right
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] left = new int[n];
        left[0]=1;
        for(int i=1;i<n;i++)
        {
            left[i] = nums[i-1] * left[i-1];
        }
        int res = 1;
        for(int i=n-2;i>=0;i--)
        {
            res = res * nums[i+1];
            left[i] *= res;
        }
        return left;
    }
}

239. 滑动窗口最大值

滑动窗口/双指针 + 单调队列

  • 窗口长度为k,初始化left、right指针
    • right指针指向0,表示要出窗口的值
    • left指针初始化1-k
      • r-l+1=k–> l = k-1
  • 当r值要入窗口前,维护单调队列(不严格递减队列),也就是但较大值coming,维护队列单调性(双端队列)
    • r要入窗口,要将queue尾部所有小于r的值弹出,才能入窗口
  • l-1>=0代表当r入窗口,要出窗口的值
    • 若出窗口的l-1的值为max(queue.peekFirst),那么队列移除最大值
  • 当l==0,代表已经初始化窗口,可以开始收割结果queue.peekFirst
class Solution {
    Deque<Integer> queue = new LinkedList<>();
    public int[] maxSlidingWindow(int[] nums, int k) {
        int r=0,l=1-k,n=nums.length;
        int[] res = new int[n-k+1];
        int index = 0;
        for(int i=0;i<n;i++)
        {
            if(l-1>=0 && nums[l-1]==queue.peekFirst())
            {
                queue.pollFirst();
            }
            while(!queue.isEmpty() && nums[r]>queue.peekLast())
            {
                queue.pollLast();
            }
            queue.add(nums[r]);
            if(l>=0)
            {
                res[index++] = queue.peek();
            }
            l++;
            r++;
        }
        return res;
    }
}

240. 搜索二维矩阵 II

代码思路

  • 二分查找的关键是确定可能区间,然后再是单调性
    • 为什么这么说?题33. 搜索旋转排序数组
  • 如何不断缩小值可能出现的范围,并且保证丢弃的范围是绝不可能存在目标值的,这才是二分的思想
    • 每行的元素从左到右升序排列
    • 每列的元素从上到下升序排列
  • 利用单调性可以不断缩小搜索区间
    • 初始化时,选择一个当前元素的中点,也就是nums[n-1][0],这是由于我们的策略时从左下到右上搜索,做到不遗漏
      • 也可以从右上角到左下角进行搜索
  • 具体步骤
    • 当前搜索到的元素ele==target ,return
    • 当前搜索到的元素ele>target ,到右边搜索,y++
    • 当前搜索到的元素ele
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int n = matrix.length,m=matrix[0].length;
        int x = n-1,y=0;
        while(x>=0 && y<m)
        {
            int cur = matrix[x][y];
            if(cur==target) return true;
            if(cur>target)
            {
                x--;
            }else y++;
        }
        return false;
    }
}

剑指 Offer 16. 数值的整数次方

快速幂

  • 一个十进制的数可以用二进制的数唯一表示,例如5 = 1*1+2*0+4*1
    • 那么x^5 --> x^1 * x^4
  • 那么要计算x^5,只需要计算log5次
class Solution {
    public double myPow(double x, int n) {
        long y = n;
        double res = 1;
        if(y<0)
        {
            y = -y;
            x = 1/x;
        }
        while(y!=0)
        {
            if((y&1)==1) res*=x;
            x*=x;
            y = y>>1;
        }
        return res;
    }
}

279. 完全平方数

  • 本质上是完全背包问题,如何使用物品1,4,9,16…装满容量为V的背包的最少选择
    • bp[i]–> 表示装满背包容量为i的最少选择
    • bp[i] = Min{bp[j],bp[j-v]+1} ;(j->[v,V])
class Solution {
    public int numSquares(int n) {
        if(n<0) return 0;
        int[] bp = new int[n+1];
        Arrays.fill(bp,10000);
        bp[0]=0;
        for(int i=1;i*i<=n;i++)
        {
            for(int j=i*i;j<=n;j++)
            {
                bp[j] = Math.min(bp[j],bp[j-i*i]+1);// 每次选择完i*i后,选择次数加一
            }
        }
        return bp[n]==10000?0:bp[n];
    }
}

283. 移动零

双指针L,R

  • L表示当前可被插入非零数组的位置,从零开始
  • R遍历数组
  • 当R遍历到非零的数字时,swap(R,L),同时L++
class Solution {
    public void moveZeroes(int[] nums) {
        int l=0;
        for(int r=0;r<nums.length;r++)
        {
            if(nums[r]==0)
            {
                continue;
            }
            swap(nums,l,r);
            l++;
        }
    }
    void swap(int[] arr , int l,int r)
    {
        int t = arr[l];
        arr[l] = arr[r];
        arr[r] = t;
    }
}

287. 寻找重复数

代码思路

  • 反转确定
    • 如果一个元素出现,那么将元素状态反转
      • 当元素状态被反转两次,那么出现重复数字
class Solution {
    public int findDuplicate(int[] nums) {
        int n=nums.length;
        boolean[] used = new boolean[n+1];
        for(int num : nums)
        {
            used[num] = !used[num];
            if(!used[num]) return num;
        }
        return -1;
    }
}

300. 最长递增子序列

动态规划

  • bp[i]表示: 以下标i结尾的的数组,最长递增子序列的长度
    • bp[n-1]: 表示以下标n-1结尾的的数组,最长递增子序列的长度,但是最长递增子序列不一定以n-1为结尾,所以不是答案
  • bp[i] = Math{bp[0::j]}+1, 0<=j
  • 初始化bp[0] = 1
class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] bp = new int[n];
        bp[0] = 1;
        int res=bp[0];
        for (int i = 1; i < n; i++) {
            int max = 0;
            for (int j = 0; j < i; j++) {
                if(nums[i]>nums[j]){
                    max = Math.max(max,bp[j]);
                }
            }
            bp[i] = Math.max(max,bp[i])+1;
            res = Math.max(res,bp[i]);
        }
        return res;
    }
}

301. 删除无效的括号

代码思路:

  • 用set来存储当前可能正确的括号组合,遍历set获取所有正确的括号组合
  • 若set没有出现正确的括号组合怎么办?
    • 对set可能的括号组合–> 删除一个字符,形成新的括号组合
    • 为了保证不漏
      • 获取可能组合时,获取新的括号组合是获取每个位置都删除一个字符的所有可能
class Solution {
    public List<String> removeInvalidParentheses(String s) {
        List<String> ans = new ArrayList<String>();
        Set<String> currSet = new HashSet<String>();

        currSet.add(s);
        while (true) {
            for (String str : currSet) {
                if (isValid(str)) {
                    ans.add(str);
                }
            }
            if (ans.size() > 0) {
                return ans;
            }
            Set<String> nextSet = new HashSet<String>();
            for (String str : currSet) {
                for (int i = 0; i < str.length(); i ++) {
                    if (str.charAt(i) == '(' || str.charAt(i) == ')') {
                        nextSet.add(str.substring(0, i) + str.substring(i + 1));
                    }
                }
            }
            currSet = nextSet;
        }
    }

    private boolean isValid(String str) {
        char[] ss = str.toCharArray();
        int count = 0;

        for (char c : ss) {
            if (c == '(') {
                count++;
            } else if (c == ')') {
                count--;
                if (count < 0) {
                    return false;
                }
            }
        }
        return count == 0;
    }
}

322. 零钱兑换

完全背包

  • coins表示物品,可以任意拿
  • amount表示背包容量
  • bp[i] = Min{bp[i],bp[i-coin]+1}
  • 初始化bp[i]=MAX,bp[0]=0
class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] bp = new int[amount+1];
        Arrays.fill(bp,10000000);
        bp[0]=0;
        for(int coin : coins)
        {
            for(int i=coin;i<=amount;i++)
            {
                bp[i] = Math.min(bp[i],bp[i-coin]+1);
            }
        }
        return bp[amount]==10000000?-1:bp[amount];
    }
}

337. 打家劫舍 III

class Solution {
    public int rob(TreeNode root) {
        HashMap<TreeNode,Info> map = new HashMap<>();
        return Math.max(p(root,map).get,p(root,map).no);
    }
    private Info p(TreeNode root,HashMap<TreeNode,Info> map) {
        if(root==null) {
            return new Info(0,0);
        }
        if(map.containsKey(root)){
            return map.get(root);
        }
        Info info = new Info();
        info.get = root.val+p(root.left,map).no+p(root.right,map).no;
        info.no = Math.max(p(root.left,map).no,p(root.left,map).get)+Math.max(p(root.right,map).get,p(root.right,map).no);
        map.put(root,info);
        return info;
    }
}
class Info{
    int get;
    int no;

    public Info() {
    }

    public Info(int get, int no) {
        this.get = get;
        this.no = no;
    }
}

338. 比特位计数

对于任意整数 x,令 x=x&(x-1),该运算将 x的二进制表示的最后一个 1 变成 0。

class Solution {
    public int[] countBits(int n) {
        int[] bp = new int[n+1];
        for(int i=1;i<=n;i++)
        {
            bp[i] = getCount(i);
        }
        return bp;
    }
    int getCount(int num)
    {
        int res = 0;
        while(num!=0)
        {
            num = (num)&(num-1);
            res++;
        }
        return res;
    }
}

347. 前 K 个高频元素

快排 + hashMap

  • 通过hashMap将数组元素和出现次数统计后形成新的数组Node[]
  • 使用快排,前k的高频元素指的就是下标位置为k-1的前面的数据
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        HashMap<Integer,Integer> dic = new HashMap<>();
        for(int num : nums)
        {
            dic.put(num,dic.getOrDefault(num,0)+1);
        }
        int size = dic.size();
        Node[] nodes = new Node[size];
        int index = 0;
        for(int key : dic.keySet())
        {
            nodes[index++] = new Node(key,dic.get(key));
        }
        // Arrays.sort(nodes,(a,b)->b.cnt-a.cnt);
        int tar = k-1,l=0,r=size-1;
        while(l<=r)
        {
            int cur = partition(nodes,l,r);
            if(cur==tar) break;
            else if(cur>tar)
            {
                r = cur-1;
            }else{
                l = cur+1;
            }
        }
        int[] res = new int[k];
        for(int i=0;i<k;i++)
        {
            res[i] = nodes[i].val;
        }
        return res;
    }
    int partition(Node[] nodes , int lo,int ro)
    {
        if(lo>ro) return -1;
        Node divi=nodes[lo];
        int l=lo,r=ro;
        while(true)
        {
            while(l<=r && nodes[l].cnt>=divi.cnt) l++;
            while(l<=r && nodes[r].cnt<divi.cnt) r--;
            if(l>r) break;
            swap(nodes,l,r);
        }
        swap(nodes,lo,r);
        return r;
    }
    void swap(Node[] nodes , int l,int r)
    {
        Node temp = nodes[l];
        nodes[l]=nodes[r];
        nodes[r] = temp;
    }
}
class Node{
    int val;
    int cnt;
    public Node(int val,int cnt)
    {
        this.val = val;
        this.cnt = cnt;
    }
}

当然可以直接排序node[]数组

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        HashMap<Integer,Integer> dic = new HashMap<>();
        for(int num : nums)
        {
            dic.put(num,dic.getOrDefault(num,0)+1);
        }
        int size = dic.size();
        Node[] nodes = new Node[size];
        int index = 0;
        for(int key : dic.keySet())
        {
            nodes[index++] = new Node(key,dic.get(key));
        }
        Arrays.sort(nodes,(a,b)->b.cnt-a.cnt);
        int[] res = new int[k];
        for(int i=0;i<k;i++)
        {
            res[i] = nodes[i].val;
        }
        return res;
    }
}
class Node{
    int val;
    int cnt;
    public Node(int val,int cnt)
    {
        this.val = val;
        this.cnt = cnt;
    }
}

406. 根据身高重建队列

  • 按照排序规则:先按照身高逆序排,身高相同按照位置正序排
  • 排完之后,按照位置插入即可
class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people,(arr,brr)->{
            return arr[0]!=brr[0]?brr[0]-arr[0]:arr[1]-brr[1];
        });
        List<int[]> res = new LinkedList<>();
        for(int[] arr : people)
        {
            res.add(arr[1],arr);
        }
        return res.toArray(new int[0][]);
    }
}

416. 分割等和子集

01背包

  • 假设可以分平为等量数据,那么sum一定是偶数
  • 平分为俩分,一份占V,其实就是背包容量
  • 尽量装满背包V,若背包被刚好装满,则可以被等量拆分
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = Arrays.stream(nums).sum();
        if(sum%2!=0) return false;
        int tar = sum/2;
        int[] bp = new int[tar+1];
        for(int num : nums)
        {
            for(int i=tar;i>=num;i--)
            {
                bp[i] = Math.max(bp[i],bp[i-num]+num);
            }
        }
        return bp[tar]==tar;
    }
}

437. 路径总和 III

前序遍历

class Solution {
    public int pathSum(TreeNode root, int targetSum) {
        if(root==null) return 0;
        return dsf(root,targetSum)+pathSum(root.left,targetSum)+ pathSum(root.right,targetSum);
    }
    int dsf(TreeNode root,long targetSum)
    {
        int res=0;
        if(root==null)
        {
            return 0;
        }
        targetSum-=root.val;
        if(targetSum==0)
        {
            res++;
        }
        res += dsf(root.left,targetSum);
        res += dsf(root.right,targetSum);
        targetSum+=root.val;
        return res;
    }
}

438. 找到字符串中所有字母异位词

字母异位词,可以有一个唯一的按照字母序排列的字符串A与之对应

  • 通过数组获取每个字符串的唯一对应的A,作为key,存储在hashMap中
  • 遍历map.values
class Solution {
    List<Integer> res = new ArrayList<>();
    public List<Integer> findAnagrams(String s, String p) {
        if(p.length()==0 || s.length()==0 || p.length()>s.length()) return res;
        int[] tar = new int[26];
        int[] window = new int[26];
        int pl = p.length();
        int sl = s.length();
        for(int i=0;i<pl;i++)
        {
            tar[p.charAt(i)-'a']++;
            window[s.charAt(i)-'a']++;
        }
        if(Arrays.equals(tar,window)) res.add(0);
        for(int i=1;i<=sl-pl;i++)
        {
            // i-1 out
            window[s.charAt(i-1)-'a']--;
            // i+1 in
            window[s.charAt(i+pl-1)-'a']++;
            if(Arrays.equals(tar,window)) res.add(i);
        }
        return res;
    }
}

448. 找到所有数组中消失的数字

策略

  • 将数组下标作为元素的映射,例如num–>num-1的数组下标上
  • 每次找到一个num,将对应数组下标的值取负数,意味着该数组单元的值找到
  • 遍历完成,只需要取出元素的值等于0的下标值+1就是消失的数字
class Solution {
    List<Integer> res = new ArrayList<>();
    public List<Integer> findDisappearedNumbers(int[] nums) {
        int n = nums.length;
        for(int i=0;i<nums.length;i++)
        {
            int find =  Math.abs(nums[i]);
            nums[find-1] = -Math.abs(nums[find-1]);
        }
        for(int i=0;i<n;i++)
        {
            if(nums[i]>=0) res.add(i+1);
        }
        return res;
    }
}

461. 汉明距离

取出数字2进制位置上的最后一位

每次加上两两异或的结果(不同为1,相同为0)

class Solution {
    public int hammingDistance(int x, int y) {
        int ans=0;
        while(x!= 0|| y!=0)
        {
            int xa = (x&1),ya=(y&1);
            ans += (xa^ya)==1?1:0;
            x = x>>1;
            y = y>>1;
        }
        return ans;
    }
}

494. 目标和

  • 和为sum,假设正数和为left,负数和为right(不考虑符号)
    • sum = left+right
    • left - right = k
    • 可以的出,left = (sum+k)/2
  • 那么实际上求的是,在nums元素中取,构成left的组合数
    • 01背包求组合数问题
  • bp[j] += bp[j-nums[i]];
  • 初始化bp[0]=1
  • 当(sum+k)不能被2整除,代表不能构成left,返回0
class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = Arrays.stream(nums).sum();
        int bag = (sum+target)/2;
        if((sum+target)%2!=0) return 0;
        if(bag<0) bag = -bag;
        int[] bp = new int[bag+1];
        bp[0]=1;
        for(int i=0;i<nums.length;i++)
        {
            for(int j=bag;j>=nums[i];j--)
            {
                bp[j] += bp[j-nums[i]];
            }
        }
        return bp[bag];
    }
}

543. 二叉树的直径

class Solution {
    int ans = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        int h = dsf(root);
        return ans;
    }

    private int dsf(TreeNode root) {
        if(root==null){
            return 0;
        }
        int left = dsf(root.left);
        int right = dsf(root.right);
        int max = Math.max(left, right);
        ans = Math.max(ans,left+right);
        return max+1;
    }
}

560. 和为 K 的子数组

前缀和 + hashMap

  • 搞定清楚hashMap中存储的是什么?
    • 本道题,hashMap存储的是【和为key的子数组的个数】
  • 前缀和:sum[i::j] = sum[j+1]-sum[i]
class Solution {
    public int subarraySum(int[] nums, int k) {
        // dic : how many sum coming times
        Map<Integer,Integer> dic = new HashMap<>();
        int n = nums.length;
        int sum=0;
        dic.put(0,1);// 表示前缀和为0的子数组的个数现在有一个
        int ans=0;
        for(int num : nums)
        {
            sum += num;
            int tar = sum-k;
            ans += dic.getOrDefault(tar,0); // 查看是否存在tar,能够使得当前前缀和sum-tar==k
            dic.put(sum,dic.getOrDefault(sum,0)+1);// 维护dic
        }
        return ans;
    }
}

581. 最短无序连续子数组

拷贝排序后,记录出现不一致位置的最大值和最小值

class Solution {
    public int findUnsortedSubarray(int[] nums) {
        int n = nums.length;
        int[] cp = Arrays.copyOf(nums,n);
        Arrays.sort(nums);
        int res = 0;
        int l=n,r=-1;
        for(int i=0;i<n;i++)
        {
            if(nums[i]!=cp[i]) {
                l = Math.min(l,i);
                r = Math.max(r,i);
            }
        }
        if(l==n || r==-1) return 0;
        return r-l+1;
    }
}

647. 回文子串

区间dp

当s[i]==s[j]  dp[i][j] = dp[i+1][j-1]
class Solution {
    public int countSubstrings(String s) {
        int n = s.length();
        boolean[][] bp = new boolean[n+1][n+1];
        bp[0][0] = true;
        int res=1;
        for(int len=1;len<=n;len++)
        {
            for(int i=0;i<=n-len;i++)
            {
                int j = i+len-1;
                if(s.charAt(i)==s.charAt(j))
                {
                    if(len<=3) bp[i][j] = true;
                    else bp[i][j] = bp[i+1][j-1];
                    if(bp[i][j]) res++;
                }
            }
        }
        return res-1;
    }
}

739. 每日温度

单调栈:

  • 单调递减的栈(不严格)
  • 遇到高温度H,将可以确定的日期弹出即可
    • 可以确定的日期指的是,温度值小于H的值
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        // deal when the bigger ele coming
        Stack<Integer> stack = new Stack<>();
        int n = temperatures.length;
        int[] res = new int[n];
        for(int i=0;i<n;i++)
        {
            if(stack.isEmpty())
            {
                stack.add(i);
                continue;
            }
            while(!stack.isEmpty() && temperatures[i]>temperatures[stack.peek()])
            {
                int pre = stack.pop();
                res[pre] = i - pre;
            }
            stack.add(i);
            
        }
        return res;
    }
}

你可能感兴趣的:(java,leetcode,算法,职场和发展,java)