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&©Head!=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