Lubuladong算法小抄思考和题集

0.引言

  • 本文主要针对Labuladong算法小抄中设计的LeetCode提集进行整理,并给出相关题目解法。小编欢迎大佬指出其中问题,可随时通过邮件联系小编:xiakexiaohu#163.com。

1.算法核心关键点

  • TODO

2.题集及解法

  • 1.两数之和
class Solution {
    public int[] twoSum(int[] nums, int target) {
        if(nums==null||nums.length==0){
            return new int[0];
        }

        Map<Integer,Integer> existMap=new HashMap<>();
        for(int i=0;i<nums.length;i++){
            int remain=target-nums[i];
            if(existMap.containsKey(remain)){
                return new int[] {i,existMap.get(remain)};
            }else{
                existMap.put(nums[i],i);
            }
        }
        return new int[0];
    }
}
  • 3.无重复字符的最长子串
class Solution {
    public int lengthOfLongestSubstring(String s) {
        // 典型的字典数组,我们用firstIndex记录第一个开始的元素,用lastRepeatIndex记录重复元素下标
        if(s==null||s.length()==0){
            return 0;
        }

        char [] chs=s.toCharArray();
        int [] indexArr=new int[256];
        int firstIndex=0;
        Arrays.fill(indexArr,-1);
        int lastRepeatIndex=0;
        // 第一个元素默认下标为0
        indexArr[chs[firstIndex]]=0;
        int res=1;

        for(int i=1;i<chs.length;i++){
            char ch=chs[i];
            if(indexArr[ch]!=-1){
                // 说明存在重复
                lastRepeatIndex=indexArr[ch];
                if(lastRepeatIndex>=firstIndex){
                    // 说明第一个元素下标需要更新为开始重复元素的下一个
                    firstIndex=lastRepeatIndex+1;
                }
            }
            indexArr[ch]=i;

            res=Math.max(res,i-firstIndex+1);
        }
        return res;
    }
}
  • 5.最长回文子串
class Solution {
    public String longestPalindrome(String s) {
        // 典型的回文字符串判断,我们用curLength来记录已经查询到的最长回文长度,需要考虑两种情况:
        // 1.只有一个单词 i-curlength,则cuLength+1
        // 2.有偶数的单词 i-curlength-1,则curLength+2
        // 我们分别使用stratIndex和endIndex来记录最长回文字符串开始和结束下标
        // 3.我们定义一个isPalindrome的方法来判断是否为回文串
        if(s==null||s.length()==0){
            return "";
        }

        int startIndex=0;
        // endIndex为左开右闭
        int endIndex=0;
        int curMaxPalinLength=0;
        char [] chs=s.toCharArray();
        for(int i=0;i<chs.length;i++){
            if(isPalindrome(chs,i-curMaxPalinLength,i)){
                startIndex=i-curMaxPalinLength;
                endIndex=i+1;
                curMaxPalinLength++;
            }else if(isPalindrome(chs,i-curMaxPalinLength-1,i)){
                startIndex=i-curMaxPalinLength-1;
                endIndex=i+1;
                curMaxPalinLength+=2;
            }
        }
        return s.substring(startIndex,endIndex);
    }

    private boolean isPalindrome(char [] chs,int start,int end){
        if(start<0||end>chs.length-1){
            return false;
        }

        while(start<=end){
            if(chs[start]==chs[end]){
                start++;
                end--;
            }else{
                // 不相等
                return false;
            }
        }
        return true;
    }
}
  • 10.正则表达式匹配
  • 15.三数之和
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        // 典型的三指针的解法,需要考虑去重
        if(nums==null||nums.length==0||nums.length<3){
            return new ArrayList<>();
        }

        // 我们先排序,确保相同的元素均在一起
        Arrays.sort(nums);

        List<List<Integer>> res=new ArrayList<>();
        int target=0;
        for(int firstIndex=0;firstIndex<nums.length-2;firstIndex++){
            // 跳过相同
            if(nums[firstIndex]>0||firstIndex>0&&nums[firstIndex]==nums[firstIndex-1]){
                // 后面元素均大于0,或者同前面一个元素相同
                continue;
            }

            int remain=target-nums[firstIndex];
            int second=firstIndex+1;
            int third=nums.length-1;
            while(second<third){
                // 先添加元素,如果找到,则需要跳过重复的
                if(nums[second]+nums[third]==remain){
                    res.add(Arrays.asList(nums[firstIndex],nums[second++],nums[third--]));
                    // 中间指针
                    while(second<third&&nums[second]==nums[second-1]){
                        second++;
                    }
                    // 尾指针
                    while(third>second&&nums[third]==nums[third+1]){
                        third--;
                    }
                }else if(nums[second]+nums[third]<remain){
                    second++;
                }else{
                    third--;
                }
            }
        }
        return res;
    }
}
  • 18.四数之和
  • 20.有效的括号
  • 22.括号生成
  • 25.K 个一组翻转链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        // 思路:典型的快慢指针,每次反转k个元素,我们用removeCount来表示反转k的元素值,如果removeCount>0表示默认这段不够k个,无需要反转
        if(head==null||k<=1){
            return head;
        }

        ListNode preHead=new ListNode();
        ListNode res=preHead;
        preHead.next=head;
        ListNode fastNode=head;
        while(fastNode!=null){
            // 每次移动k元素
            int removeCount=k;
            while(removeCount>0&&fastNode!=null){
                fastNode=fastNode.next;
                removeCount--;
            }

            if(removeCount>0){
                // 表示末尾这段不够k个,不需要反转
                break;
            }

            // 开始反转前k个元素
            while(head!=null&&head.next!=fastNode){
                ListNode next=head.next;
                head.next=next.next;
                next.next=preHead.next;
                preHead.next=next;
            }
            // 已经反转完前k段,则重置preHead和head
            preHead=head;
            head=fastNode;
        }
        return res.next;
    }
}
  • 26.删除排序数组中的重复项
class Solution {
    public int removeDuplicates(int[] nums) {
        // 典型的往前移动操作,只需要统计前面已经重复的count,那么每个元素都往前移动对应的次数
        if(nums==null||nums.length==0){
            return 0;
        }

        if(nums.length==1){
            return 1;
        }

        int duplicateCount=0;
        for(int i=1;i<nums.length;i++){
            if(nums[i]==nums[i-1]){
                // 统计重复次数
                duplicateCount++;
            }
            // 移动至前面重复次数的下标
            nums[i-duplicateCount]=nums[i];
        }
        return nums.length-duplicateCount;
    }
}
  • 34.在排序数组中查找元素的第一个和最后一个位置
  • 37.解数独
  • 42.接兩水
  • 45.跳跃游戏I
  • 46.全排列
  • 51.N 皇后
  • 53.最大子序和
  • 55.跳跃游戏
  • 56.合并区间
  • 72.编辑距离
class Solution {
    public int minDistance(String word1, String word2) {
        // 非常经典的dp问题,我们需要定义dp[i][j]标识字符串str1的i,到str2的j两端字符串的最小步骤
        // dp四要素:1.边界值,即i=0,j步骤插入称为str2.j=0,i步骤删除成为str2
        // 2.状态,即我们需要str1和str2的长度
        // 3.选择:1.相同,则i和j都往前一步。2.不同:1.选择删除一个、插入一个、替换一个,选择这三者之中最好的作为本次操作的结果
        if(word1==null&&word2!=null){
            return word2.length();
        }

        if(word1!=null&&word2==null){
            return word1.length();
        }

        int len1=word1.length();
        int len2=word2.length();
        // 判断前面i-1是否相等,整体数组长度len1+1,为了对i=0的时候判断i-1进行处理,i=1开始处理可以减少越界处理,可以按照自己需求对i从0开始判断
        int [][] dp=new int[len1+1][len2+1];

        // 1.边界值
        for(int i=1;i<=len1;i++){
            // 删除i个元素长度
            dp[i][0]=i;
        }

        for(int j=1;j<=len2;j++){
            // 插入j个元素长度
            dp[0][j]=j;
        }

        // 状态和选择
        char [] chs1=word1.toCharArray();
        char [] chs2=word2.toCharArray();
        for(int i=1;i<=len1;i++){
            for(int j=1;j<=len2;j++){
                if(chs1[i-1]==chs2[j-1]){
                    // 相同,相当于同前面相等
                    dp[i][j]=dp[i-1][j-1];
                }else{
                    // 不同,则需要判断三种选择的最小值,同时步骤+1
                    dp[i][j]=min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])+1;
                }
            }
        }

        // 最终返回结果
        return dp[len1][len2];
    }

    private int min(int a,int b,int c){
        return Math.min(Math.min(a,b),c);
    }
}
  • 76.最小覆盖子串
  • 77.组合
  • 78.子集
  • 83.删除排序链表中的重复元素
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // 思路:典型的链表移除,需要注意大于等于三个元素都相同的情况,1,1,1->1
        if(head==null){
            return head;
        }

        ListNode res=head;
        while(head!=null&&head.next!=null){
            if(head.val==head.next.val){
                // 相同的元素,原地不动,直接对比
                head.next=head.next.next;
            }else{
                head=head.next;
            }   
        }
        return res;
    }
}
  • 92.反转链表II
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        // 典型的链表反转操作,我们只需要找到开始反转的头结点即可,循环right-left次数的插入至头结点即可
        if(head==null){
            return head;
        }

        ListNode preHead=new ListNode();
        preHead.next=head;
        ListNode curHead=preHead;
        // 当前的头结点
        for(int i=1;i<=left-1;i++){
            curHead=curHead.next;
        }

        if(curHead!=null){
            // 开始反转
            ListNode next=curHead.next;
            int times=right-left;
            while(times-->0&&next!=null&&next.next!=null){
                ListNode nextNext=next.next;
                next.next=nextNext.next;
                nextNext.next=curHead.next;
                curHead.next=nextNext;
            }
        }
        return preHead.next;
    }
}
  • 98.验证二叉搜索树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        // 思路:典型的递归,我们每次判断需要记录最大值和最小值,同时在递归的过程中更新最大值和最小值
        long min=Long.MIN_VALUE;
        long max=Long.MAX_VALUE;
        return recursive(root,min,max);
    }

    public boolean recursive(TreeNode root,long min,long max){
        if(root==null){
            return true;
        }

        // root节点应该满足大于左子树,小于等于右子树
        if(!(root.val>min&&root.val<max)){
            return false;
        }

        // 递归判断左右子树
        return recursive(root.left,min,root.val)&&recursive(root.right,root.val,max);
    }
}
  • 100.相同的树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        // 典型的递归
        if(p==null&&q!=null){
            return false;
        }

        if(p!=null&&q==null){
            return false;
        }

        if(p==null||q==null){
            return true;
        }

        if(p.val!=q.val){
            return false;
        }
        // 左右子树递归
        return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
    }
}
  • 111.二叉树的最小深度
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int minDepth(TreeNode root) {
        // 树的高度,即根节点到叶子节点的高度
        if(root==null){
            return 0;
        }

        // 分别代表左右子树的高度
        int leftDepth=minDepth(root.left);
        int rightDepth=minDepth(root.right);

        // 判断左右子树是否为空,若单支为空,则取另一边作为本节点的高度
        if(root.left==null){
            return rightDepth+1;
        }

        if(root.right==null){
            return leftDepth+1;
        }

        // 左右子树均不为空,则分别递归左右子树
        return Math.min(leftDepth,rightDepth)+1;
    }
}
  • 130.被围绕的区域
  • 141.环形链表I
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        // 思路:典型的快慢指针,当两指针相遇时,则表示存在环,否则不存在
        if(head==null){
            return false;
        }

        ListNode slow=head;
        ListNode fast=head.next!=null?head.next:null;
        while(slow!=null&&fast!=null){
            if(fast==slow){
                return true;
            }
            fast=fast.next!=null?fast.next.next:null;
            slow=slow.next;
        }
        return false;
    }
}
  • 142.环形链表II
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        // 思路:典型的快慢指针,我们假设整个环长度为k,其中环的部分为m,那么环和非环的接触点的长度就是k-m
        // 我们先得出环的长度m,然后slow和fast指针全部从头开始一步一走,fast先走m步,然后当fast==slow时候就表示slow也走了k-m步,也就是环和非环的相遇点了。
        if(head==null){
            return head;
        }

        ListNode slow=head;
        ListNode fast=head;
        ListNode firstMeetPoint=null;
        while(slow!=null&&fast!=null){
            slow=slow.next;
            fast=fast.next!=null?fast.next.next:null;

            if(slow==fast){
                firstMeetPoint=slow;
                break;
            }
        }

        // 即不存在环
        if(firstMeetPoint==null){
            return null;
        }

        slow=firstMeetPoint.next;
        // 求环的长度
        int len=1;
        while(slow!=firstMeetPoint){
            slow=slow.next;
            len++;
        }

        slow=head;
        fast=head;
        // fast先走len长度
        while(len-->0){
            fast=fast.next;
        }

        // 快慢指针同时一步一步的前进
        while(slow!=fast){
            slow=slow.next;
            fast=fast.next;
        }
        return slow;
    }
}
  • 146.LRU 缓存机制
  • 167.两数之和工-输人有序数组
  • 170.两数之和III-数据结构设计
  • 198.打家劫舍
class Solution {
    public int rob(int[] nums) {
        // 典型的贪心问题,我们使用dp来记录
        if(nums==null||nums.length==0){
            return 0;
        }

        int [] dp=new int[nums.length];
        dp[0]=nums[0];
        if(nums.length<=1){
            return dp[nums.length-1];
        }

        dp[1]=Math.max(nums[0],nums[1]);
        for(int i=2;i<nums.length;i++){
            // 不打劫这家 dp[i-1]
            // 打劫这家 dp[i-2]+nums[i]
            // 去选择两者之间的最大值
            dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[nums.length-1];
    }
}


class Solution {
    public int rob(int[] nums) {
        // 典型的贪心算法,只需要定义个打劫合数组dp[i]表示打劫到i的时候最大值
        // 思路:1.边界值 dp[0]=0,即第0个还没有开始
        // 2.状态 打家到最后一家
        // 3.选择:1.打劫当前这家。2.不打劫
        if(nums==null||nums.length==0){
            return 0;
        }
        int [] dp=new int[nums.length+1];
        // 边界值
        dp[0]=0;
        dp[1]=nums[0];

        for(int i=2;i<=nums.length;i++){
            // 如果打劫当前这家那就只能和i-2一起,或者不打劫这家就取i-1的最大值作为打劫目前的最大值
            // nums[i-1]因为下标从i-1开始
            dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i-1]);
        }

        return dp[nums.length];
    }
}
  • 204.计数质数
  • 213.打家劫舍II
class Solution {
    public int rob(int[] nums) {
        // 典型的贪心算法,但是考虑到是环,需要对当前打劫到的金额进行记录(放在一个当前位置可获得最大金额数组中)
        // 我们分两种情况开始讨论:
        // 1.从0开始抢劫,则我们只能抢劫到倒数第二家
        // 2.从1开始抢劫,则我们可以抢到到倒数第一家
        // 最后,输出两次情况的最大值
        if(nums==null||nums.length==0){
            return 0;
        }

        if(nums.length==1){
            return nums[0];
        }

        if(nums.length==2){
            return Math.max(nums[0],nums[1]);
        }
        // 从0开始
        int maxAmountZero=getMaxAmount(nums,0,nums.length-2);
        int maxAmountOne=getMaxAmount(nums,1,nums.length-1);
        return Math.max(maxAmountZero,maxAmountOne);
    }

    private int getMaxAmount(int []nums,int start,int end){
        if(start>=end){
            return 0;
        }

        int [] maxAmount=new int[nums.length];
        maxAmount[start]=nums[start];
        maxAmount[start+1]=Math.max(nums[start],nums[start+1]);
        for(int i=start+2;i<=end;i++){
            // 正常贪心
            maxAmount[i]=Math.max(maxAmount[i-1],maxAmount[i-2]+nums[i]);
        }
        return maxAmount[end];
    }
}
  • 222.完全二叉树的节点个数
  • 224.基本计算器
  • 227.基本计算器I
  • 234.回文链表
// 利用快慢指针-更快
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        // 回文意味着对半是相同的,我们只需要利用快慢指针,将后半部分反转后然后分别两端分别从头开始遍历,当遍历到不同则认为不是回文
        if(head==null){
            return true;
        }

        ListNode slow=head;
        ListNode fast=head;
        while(fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }

        // 防止只有一个元素
        if(fast!=null){
            slow=slow.next;
        }

        // 此时开始反转后半部分
        slow=reverse(slow);
        while(slow!=null&&head.val==slow.val){
            slow=slow.next;
            head=head.next;
        }
        // 如果顺利遍历到末尾,说明是回文
        return slow==null;
    }

    private ListNode reverse(ListNode head){
        ListNode pre=null;
        while(head!=null){
            ListNode next=head.next;
            head.next=pre;
            pre=head;
            head=next;
        }
        return pre;
    }
}

// 反向插入生成一条反向的链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        // 回文即从左往右和从右往左读都是一样的,我们只需要按照已有链表反向生成链表
        if(head==null){
            return true;
        }

        ListNode copyPreHead=new ListNode();
        ListNode curHead=head;
        while(curHead!=null){
            ListNode newNode=new ListNode(curHead.val);
            // 将新生成节点插入copy链表中
            newNode.next=copyPreHead.next;
            copyPreHead.next=newNode;

            curHead=curHead.next;
        }

        // 开始判断对比
        ListNode copyHead=copyPreHead.next;
        while(head!=null&&copyHead!=null){
            if(head.val!=copyHead.val){
                return false;
            }
            head=head.next;
            copyHead=copyHead.next;
        }
        return true;
    }
}
  • 236.二叉树的最近公共祖先
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 思路:典型的递归,后序遍历该树节点,即需要我们判断如下情况:
        // 1.p和q分别在root的左右子树中,则返回root,因为root就是其根节点
        // 2.p和q都不在root的左右子树中,则返回null,说明不存在最近公共父几点
        // 3.p和q只有一个存在于root的中,那么返回该节点即可

        // 定义出口
        if(root==null){
            return null;
        }

        if(root==p||root==q){
            return root;
        }

        // 后序遍历
        TreeNode leftCommonNode=lowestCommonAncestor(root.left,p,q);
        TreeNode rightCommonNode=lowestCommonAncestor(root.right,p,q);

        // 处理 情况1-存在root的左右子树中
        if(leftCommonNode!=null&&rightCommonNode!=null){
            return root;
        }

        // 情况2-不存在以root的节点子树
        if(leftCommonNode==null&&rightCommonNode==null){
            return null;
        }

        // 情况3-返回不为空的节点
        return leftCommonNode==null?rightCommonNode:leftCommonNode;
    }
}
  • 239.滑动窗口最大值
  • 292.Nim 游戏
  • 297.二叉树的序列化和反序列化
  • 300.最长上升子序列
  • 312.戳气球
  • 319.灯泡开关
  • 322.零钱兑换
class Solution {
    public int coinChange(int[] coins, int amount) {
        if(coins==null||coins.length==0){
            return -1;
        }

        // dp[i]表示当前金额为i放入的最小硬币数量
        int [] dp=new int[amount+1];
        Arrays.fill(dp,amount+1);
        // 边界值,金额为0的时候没有硬币
        dp[0]=0;
        for(int i=0;i<coins.length;i++){
            for(int j=1;j<=amount;j++){
                if(j>=coins[i]){
                    // 说明当前硬币可以放入,那我们取当前dp[i]和dp[i-coins[i]]+1两者之间最小值作为结果
                    dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
                }
            }
        }
        return dp[amount]==amount+1?-1:dp[amount];
    }
}
  • 337.打家劫舍I
  • 341.扁平化嵌套列表迭代器
  • 354.俄罗斯套娃信封问题
  • 372.超级次方
  • 416.分割等和子集
  • 438.找到字符串中所有字母异位词
  • 450.删除二叉搜索树中的节点
  • 460.LFU 缓存机制
  • 496.下一个更大元素I
  • 503.下一个更大元素口
  • 509.斐波那契数
  • 516.最长回文子序列
class Solution {
    public int longestPalindromeSubseq(String s) {
        // 典型的动态规划,我们使用dp[i][j]来标识字符串i至字符串j之间的最长的回文子序列
        // dp的四要素:
        // 1.边界值(bad case) i=j的时候,dp[i][j]=1
        // 2.状态
            // 1.i和j相同,则只需要i+1,j-1的最长回文子序列长度+2
            // 2.i和j不相同,则dp[i+1][j],dp[i][j-1]的最长回文子序列就是dp[i][j]的最长回文序列
        // 3.选择
        // 4.定义dp数组
        if(s==null||s.length()==0){
            return 0;
        }

        char [] chs=s.toCharArray();
        int len=chs.length;
        int [][] dp=new int[len][len];
        // 边界值
        for(int i=0;i<len;i++){
            // 即i和i同一个字符串的时候,则最长回文子序列长度为1
            dp[i][i]=1;
        }

        // 状态和选择,对于i>j的下半部分我们就不用看了,因为我们要的i->j部分,可直接从下往上、从左往右遍历,最后达到dp[0][len-1]
        for(int i=len-2;i>=0;i--){
            for(int j=i+1;j<len;j++){
                if(chs[i]==chs[j]){
                    // 相等
                    dp[i][j]=dp[i+1][j-1]+2;
                }else{
                    // 不相等
                    dp[i][j]=Math.max(dp[i+1][j],dp[i][j-1]);
                }
            }
        }
        // 结果
        return dp[0][len-1];
    }
}
  • 518.零钱兑换II
// 一维数组
class Solution {
    public int change(int amount, int[] coins) {
        // 思路:典型的dp组合和问题,dp的三要素:
        // 1.边界值 dp[0] 表示金额为0的组合只有一种,那就是都不放入 所以dp[0]=1
        // 2.状态:金币、金额,因为金币是主动变量,金额为被动变量,我们将金币放在最外层循环、金额放在内层循环
        // 3.选择:1.放入:说明该金币放入可以满足,则dp[i]=dp[i]+dp[i-coins[j]],加上当前dp[i]已有的组合和。2.无法放入,则不用处理
        if(coins==null||coins.length==0){
            return 0;
        }

        int [] dp=new int[amount+1];
        // 边界值
        dp[0]=1;
        // 状态和选择
        for(int i=0;i<coins.length;i++){
            for(int j=1;j<=amount;j++){
                if(j>=coins[i]){
                    // 可放入
                    dp[j]=dp[j]+dp[j-coins[i]];
                }
            }
        }
        return dp[amount];
    }
}


// 二维数组
class Solution {
    public int change(int amount, int[] coins) {
        // 思路:典型的dp问题,我们使用二维数组dp[i][j]来表示硬币i个金额j的组合和,那么对于dp的三要素定义:
        // 1.边界值 dp[i][0]=1 表示金额为0的情况,我们不放入.dp[0][j]=0表示0个硬币,无论金额为多少,我们都无法放入
        // 2.状态:1.金币、金额,因为金币为主动变量,金额为因变量,我们将金币放在最外层循环。金额作为内循环
        // 3.选择:1.放入:则dp[i][j]=dp[i-1][j]+dp[i][j-coins[i]],表示不放入+放入的结果
        // 2.不放入:dp[i][j]=dp[i-1][j] 表示不放入的话继承dp[i-1][j]的结果
        if(coins==null||coins.length==0){
            return 0;
        }

        int n=coins.length;
        int [][]dp=new int [n+1][amount+1];
        // 边界值
        for(int i=0;i<=n;i++){
            dp[i][0]=1;
        }

        // 状态和选择
        for(int i=1;i<=n;i++){
            for(int j=1;j<=amount;j++){
                if(j>=coins[i-1]){
                    // 可放入
                    dp[i][j]=dp[i-1][j]+dp[i][j-coins[i-1]];
                }else{
                    // 无法放入
                    dp[i][j]=dp[i-1][j];
                }
            }
        }
        return dp[n][amount];
    }
}
  • 560.和为飞的子数组
  • 567.字符串的排列
  • 651.四键键盘
  • 700.二叉搜索树中的搜索
  • 701.二叉搜索树中的插入操作
  • 704.二分查找
  • 752.打开转盘锁
  • 772.基本计算器II
  • 773.滑动谜题
public static int slidingPuzzle(int[][] board) {
        // 典型BFS搜索,每次都是从0开始查找,我们将二维数组构造成一维的数组,同时构建输入board的下表的关联索引
        int m = board.length;
        int n = board[0].length;

        List<Integer>[] neighbour = new ArrayList[m * n];
        String firstStr = "";
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                firstStr += board[i][j];

                int index = i * n + j;
                List<Integer> neighbourIndex = fetchNeighbourIndex(i, j, m, n);
                neighbour[index] = neighbourIndex;
            }
        }

        // 目标值
        String target = "123450";
        Queue<String> queue = new LinkedList<>();
        Set<String> visited = new HashSet<>();
        queue.add(firstStr);
        visited.add(firstStr);

        // 开始BFS遍历
        int step = 0;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                String cur = queue.poll();
                if (target.equals(cur)) {
                    return step;
                }
                // 不匹配,则查找字符串0的
                int idx = 0;
                char[] curChs = cur.toCharArray();
                for (; curChs[idx] != '0'; idx++) {

                }

                // 将0和其相邻的数字位置进行交换
                for (int adj : neighbour[idx]) {
                    String newCur = cur;
                    char[] newCurChs = newCur.toCharArray();
                    swap(newCurChs, adj, idx);

                    // 对于已经访问的进行标记
                    newCur=String.valueOf(newCurChs);
                    if (!visited.contains(newCur)) {
                        queue.add(newCur);
                        visited.add(newCur);
                    }
                }

            }
            step++;
        }

        return -1;
    }

    private static void swap(char[] chs, int i, int j) {
        char tmp = chs[i];
        chs[i] = chs[j];
        chs[j] = tmp;
    }

    private static List<Integer> fetchNeighbourIndex(int i, int j, int m, int n) {
        List<Integer> res = new ArrayList<>();
        Pair<Integer, Integer> up = new Pair<>(i - 1, j);
        Pair<Integer, Integer> down = new Pair<>(i + 1, j);
        Pair<Integer, Integer> left = new Pair<>(i, j - 1);
        Pair<Integer, Integer> right = new Pair<>(i, j + 1);

        if (isValidPair(left, m, n)) {
            res.add(left.getKey() * n + left.getValue());
        }

        if (isValidPair(up, m, n)) {
            res.add(up.getKey() * n + up.getValue());
        }

        if (isValidPair(down, m, n)) {
            res.add(down.getKey() * n + down.getValue());
        }

        if (isValidPair(right, m, n)) {
            res.add(right.getKey() * n + right.getValue());
        }
        return res;
    }

    private static boolean isValidPair(Pair<Integer, Integer> pair, int m, int n) {
        int i = pair.getKey();
        int j = pair.getValue();
        if (i < 0 || i >= m) {
            return false;
        }

        if (j < 0 || j >= n) {
            return false;
        }
        return true;
    }
  • 855.考场就座
  • 875.爱吃香蕉的珂珂
  • 877.石子游戏
  • 887.鸡蛋掉落
  • 969.煎饼排序
class Solution {
    public List<Integer> pancakeSort(int[] arr) {
      // 典型的翻转烧饼,我们每次都选择翻转最大的,然后最大的翻转到最上面,最后再整体全部翻转一次,则最大的就在最下面了,同理,只需要对剩下的进行同样翻转即可
      if(arr==null||arr.length==0){
          return new ArrayList<>();
      }

      List<Integer> res=new ArrayList<>();
      recursive(arr,arr.length,res);
      return res;
    }

    public void recursive(int [] arr,int n,List<Integer> res){
        // 边界值,说明仅剩一个元素,不用翻转了
        if(n<=0){
            return ;
        }

        // 查找最大的
        int maxIndex=0;
        int max=0;
        for(int i=0;i<n;i++){
            if(arr[i]>arr[maxIndex]){
                maxIndex=i;
                max=arr[i];
            }
        }

        // 找到最大的,进行第一次翻转
        reverse(arr,0,maxIndex);
        // 记录翻转的下表
        res.add(maxIndex+1);
        // 第二次翻转整个部分
        reverse(arr,0,n-1);
        // 记录第二次翻转结果
        res.add(n);

        // 递归翻转剩下的
        recursive(arr,n-1,res);
    }

    public void reverse(int [] arr,int start,int end){
        while(start<end){
            int tmp=arr[start];
            arr[start]=arr[end];
            arr[end]=tmp;

            start++;
            end--;
        }
    }

}
  • 990.等式方程的可满足性
  • 1011.在D天内送达包裹的能力
  • 1118.一月有多少天
  • 1143.最长公共子序列
  • 1312.让字符串成为回文串的最少插入次数

3.思考与感悟

  • TODO
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值