我的基础算法刷题及代码详解

本文介绍了使用双指针解决字符串相加、两数之和II、替换子串得到平衡字符串和长度最小的子数组等问题,以及通过动态规划求解礼物的最大价值、最长回文子串、一和零和目标和的算法。同时,还涉及模拟方法,如扑克手牌最佳组合、顺时针打印矩阵、判断矩阵是否为X矩阵以及统计星号的数量。这些例子展示了如何运用编程技巧解决各种复杂问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

本篇博客旨在整理记录自己刷的一些基础题的思路、代码以及注解,同时希望可给小伙伴一些帮助。本人也是算法小白,水平有限,如果文章中有什么错误之处,希望小伙伴们可以在评论区指出来,共勉 💪。

一、双指针

415. 字符串相加

难度:简单

题目:给定两个字符串形式的非负整数 num1num2 ,计算它们的和并同样以字符串形式返回。

你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

提示:

  • 1 <= num1.length, num2.length <= 104
  • num1num2 都只包含数字 0-9
  • num1num2 都不包含任何前导零

解题代码:

class Solution {
    public String addStrings(String num1, String num2) {
         StringBuilder s = new StringBuilder();
        //用来记录进位
        int w = 0;
        //记录两个字符串的长度
        int len1 = num1.length(),len2 = num2.length();
        //对应俩字符串长度的最小和最大值
        int mn = Math.min(len1,len2),mx = Math.max(len1,len2);
        //遍历两个字符串尾部对齐的共同部分
        for(int m = 0; m < mn;m++){
            //获取第一个字符串最后一位对应的数值,同时指针往前移动一步
            int i = num1.charAt(len1 - 1) - '0';
            len1--;
            //获取第二个字符串最后一位对应的数值,同时指针往前移动一步
            int j = num2.charAt(len2 -1) - '0';
            len2--;
            int sum = i + j + w;
            //获取进位
            w = sum / 10;
            //加入到结果字符串中
            s.append(sum % 10);
        }
        //遍历剩余较长的字符串的前半部分
        for(int n = 0;n < mx - mn; n++){
            if(len1 > 0){//第一个字符串较长的情况
                int i = num1.charAt(len1 - 1) - '0';
                len1--;
                int sum = i + w;
                w = sum / 10;
                s.append(sum % 10);
            }else if(len2 > 0){//第二个字符串较长的情况
                int j = num2.charAt(len2 -1) - '0';
                len2--;
                int sum = j + w;
                w = sum / 10;
                s.append(sum % 10);
            }
        }
        //无论哪个更长或者相同,最后都可能产生进位,要判断是否产生进位,加上进位
        if(w != 0){
            s.append(w);
        }
        //因为是正方向防止的要逆序,而且要转换为String类型
        return s.reverse().toString();
    }
}

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

难度:中等

题目:给你一个下标从 1 开始的整数数组 numbers ,该数组已按非递减顺序排列,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。

你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

提示:

  • 2 <= numbers.length <= 3 * 104
  • -1000 <= numbers[i] <= 1000
  • numbers非递减顺序排列
  • -1000 <= target <= 1000
  • 仅存在一个有效答案

解题代码:

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        /**题意:找出数组中两个数之和为target并返回下标,要求 1 <= index1 < index2 <=numbers.length*/
        // 判断数组为空,则直接返回
        if (numbers == null) return null;
        // 定义左右两指针
        int i = 0, j = numbers.length - 1;
        while (i < j) {
            // 求和
            int sum = numbers[i] + numbers[j];
            // 判断是否相等,相等则返回,反之则递减/递增
            if (sum == target) {
                return new int[]{i+1, j+1};
            }else if (sum < target) {
                i++;
            } else {
                j--;
            }
        }
        return null;
    }
}

1234. 替换子串得到平衡字符串

难度:中等

题目:有一个只含有 'Q', 'W', 'E', 'R' 四种字符,且长度为 n 的字符串。

假如在该字符串中,这四个字符都恰好出现 n/4 次,那么它就是一个「平衡字符串」。

给你一个这样的字符串 s,请通过「替换一个子串」的方式,使原字符串 s 变成一个「平衡字符串」。

你可以用和「待替换子串」长度相同的 任何 其他字符串来完成替换。

请返回待替换子串的最小可能长度。

如果原字符串自身就是一个平衡字符串,则返回 0

提示:

  • 1 <= s.length <= 105
  • s.length4 的倍数
  • s 中只含有 'Q', 'W', 'E', 'R' 四种字符

解题代码:

class Solution {
    public int balancedString(String s) {
        //滑动窗口
        int[] counts = new int[26];  // 使用数组来存储四个字符的出现次数(使用数组便于代码书写)
        int len = s.length();   // 字符串长度
        int limit = len / 4;    // n/4
        char ch;
        // 初始化不替换内容字符出现次数数组,即初始滑动窗口维护一个空串
        for(int i = 0;i < len;i++) {
            ch = s.charAt(i);
            counts[ch - 'A']++;
        }
        // 初始化滑动窗口左右指针,维护的子串是[left,right]的内容
        // 初始化子串为空,因此left=0,right=-1表示一个空字串
        int left = 0;
        int right = -1;
        int minLength = len;  // 最小替换子串长度,初始为整个字符串长度
        // 滑动窗口
        while(left < len) {
            if(check(counts, limit)) {   // 效验通过
                // 记录当前合法子串的长度并更新最小长度
                // 左指针右移,那么原本左指针指向的字符就变成不替换的内容,不替换的内容少了一个字符,对应count数组中的值加1
                minLength = Math.min(minLength, right - left + 1);
                counts[s.charAt(left++) - 'A']++;
            }else if(right < len - 1) {
                // 当前子串不合法且右指针还没到头
                // 右指针右移,移动后的右指针指向的字符变成了子串的内容,不替换的内容少了一个字符,对应count数组中的值减1
                counts[s.charAt(++right) - 'A']--;
            }else {
                // 右指针到头,搜索结束
                break;
            }
        }
        return minLength;
    }

    /**
    效验函数,效验当前counts中四个字符的出现次数是否都小于等于limit;
    是返回true,否返回false
     */
     private boolean check(int[] counts, int limit) {
         if(counts['Q' - 'A'] > limit || counts['W' - 'A'] > limit || counts['E' - 'A'] > limit || counts['R' - 'A'] > limit) {
             return false;
         }
         return true;
     }
}

209. 长度最小的子数

难度:中等

题目:给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 105

解题代码:

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0; // 滑动窗口起始位置
        int sum = 0; // 滑动窗口数值之和
        int result = Integer.MAX_VALUE;
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= target) {
                result = Math.min(result,right - left + 1);// 取子序列长度二者的最小值
                sum -= nums[left++]; // 这里为滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列。
        return result == Integer.MAX_VALUE ? 0 :result;
    }
}

76. 最小覆盖子串

难度:困难

题目:给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

提示

  • m == s.length
  • n == t.length
  • 1 <= m, n <= 105
  • st 由英文字母组成

解题代码:

class Solution {
    public String minWindow(String s, String t) {
        if (s == null || s.length() == 0 || t == null ||t.length() == 0){
            return "";
        }
        int[] need = new int[128];
        //记录需要的字符的个数
        for (int i = 0;i < t.length(); i++) {
            need[t.charAt(i)]++;
        }
        //l是当前左边界,r是当前右边界,size记录窗口大小,count是需要的字符个数,start是最小覆盖串开始的index
        int l = 0,r = 0,size = Integer.MAX_VALUE,count = t.length(),start = 0;
        //遍历所有字符
        while(r < s.length()) {
            char c = s.charAt(r);
            if (need[c] > 0){//需要字符c
                count--;
            }
            need[c]--;//把右边的字符加入窗口
            if(count == 0) {//窗口中已经包含所有字符
                while (l < r && need[s.charAt(l)] < 0) {
                    need[s.charAt(l)]++;//释放右边移动出窗口的字符
                    l++;//指针右移
                }
                if(r - l + 1 < size) {//不能右移时候挑战最小窗口大小,更新最小窗口开始的start
                    size = r - l + 1;
                    start = l;//记录下最下值时候的开始位置,最后返回复盖串时候会用到
                }
                //l向右移动后窗口肯定不能满足了,重新开始循环
                need[s.charAt(l)]++;
                l++;
                count++;
            }
            r++;
        }
        return size == Integer.MAX_VALUE ? "" : s.substring(start, start + size);
    }
}

二、模拟

2347. 最好的扑克手牌

难度:简单

题目:给你一个整数数组 ranks 和一个字符数组 suit 。你有 5 张扑克牌,第 i 张牌大小为 ranks[i] ,花色为 suits[i]

下述是从好到坏你可能持有的 手牌类型

  1. "Flush":同花,五张相同花色的扑克牌。
  2. "Three of a Kind":三条,有 3 张大小相同的扑克牌。
  3. "Pair":对子,两张大小一样的扑克牌。
  4. "High Card":高牌,五张大小互不相同的扑克牌。

请你返回一个字符串,表示给定的 5 张牌中,你能组成的 最好手牌类型

注意: 返回的字符串 大小写 需与题目描述相同。

提示:

  • ranks.length == suits.length == 5
  • 1 <= ranks[i] <= 13
  • 'a' <= suits[i] <= 'd'
  • 任意两张扑克牌不会同时有相同的大小和花色。

解题代码:

class Solution {
    public String bestHand(int[] ranks, char[] suits) {
        char suit = suits[0]; // 记录首个花色
        // 遍历所有手牌的花色
        for(char s: suits) {
            // 判断是否有不同得花色
            if (s != suit) {
                // 花色不同就将 s 对应的花色赋值给 suit
                suit = s;
                break;
            }
        }
        // 判断五个花色是否相同,全部相同时输出 Flush
        if(suit == suits[0]) {
            return "Flush";
        }
        // 定义统计手牌大小的数组
        int[] counts = new int[13];
        // 遍历统计每个数字出现的次数
        for(int num: ranks) {
            // 手牌的值对应的数组下标自增
            counts[num - 1]++;
        }
        // 找出出现次数最多的次数
        int maxCount = 1;
        // 遍历统计好手牌大小出现次数
        for(int count: counts){
            //通过函数找出出现最多的手牌大小
            maxCount = Math.max(count, maxCount);
        }
        // 判断是否为 三条
        if (maxCount >= 3) {
            return "Three of a Kind";
        }else if (maxCount == 2){
            // 判断是否为 对子
            return "Pair";
        }else {
            //判断是否为 高牌
            return "High Card";
        }
    }
}

剑指 Offer 29. 顺时针打印矩阵

难度:简单

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

限制:

  • 0 <= matrix.length <= 100
  • 0 <= matrix[i].length <= 100

思路
根据题意可以知道打印顺序是 “从左向右、从上向下、从右向左、从下向上” 循环,同时需要考虑到“上、下、左、右”边界问题。
当它为空时,直接返回;否则按顺序打印一维数组输出。

解题代码:

class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if(matrix.length == 0) return new int[0];
        //定义l为左边、r为右边、t为上边、b为下边,x为计数器
        int l = 0,r = matrix[0].length - 1,t = 0,b = matrix.length - 1,x = 0;
        int[] res = new int[(r + 1) * (b + 1)];
        while(true) {
            //列在变,列为循环值
            for (int i = l; i <= r; i++) {
                res[x++] = matrix[t][i]; // 从左到右
            }

            //从左往右的下一步是往下走,上边界内缩,故++t
            if(++t > b) break;

            //从上往下,行在变
            for (int i = t; i <= b; i++) {
                res[x++] = matrix[i][r]; // 从上到下
            }

            //从上往下的下一步是从右往左,右边界收缩,--r
            if(l > --r) break;

            //从右向左,列在变
            for (int i = r;i >= l; i--) {
                res[x++] = matrix[b][i]; // 从右到左
            }

            //从右往左的下一步是从下往上,下边界收缩,--b
            if(t > --b) break;

            //从下到上,行在变
            for (int i = b; i >= t; i--) {
                res[x++] = matrix[i][l]; // 从下到上
            }

            //从下到上的下一步是从左到右,左边界收缩,++l
            if(++l > r) break;
        }
        return res;
    }
}

2319. 判断矩阵是否是一个 X 矩阵

难度:简单

题目:如果一个正方形矩阵满足下述 全部 条件,则称之为一个 X 矩阵

  1. 矩阵对角线上的所有元素都 不是 0
  2. 矩阵中所有其他元素都是 0

给你一个大小为 n x n 的二维整数数组 grid ,表示一个正方形矩阵。如果 grid 是一个 X 矩阵 ,返回 true ;否则,返回 false

提示:

  • n == grid.length == grid[i].length
  • 3 <= n <= 100
  • 0 <= grid[i] [j] <= 105

思路

  1. 对角线上点对应当前坐标 (i,j) 的判断:

    • 如果 i==j,说明在形如 \ 正对角线上

    • 如果 i==n-j-1,说明在形如 / 的反对角线上

  2. 对角线上为 0 的点或者非对角线上不为 0 的点是非法点,返回 false

  3. 其他情况返回 true

解题代码

class Solution {
    public boolean checkXMatrix(int[][] grid) {
        int n = grid.length;
        for(int i = 0;i < n;i++){
            for(int j = 0;j < n;j++){
                if((i==j||i==n-j-1)){
                    if(grid[i][j]==0){
                        return false;
                    }
                }else if(grid[i][j] != 0){
                    return false;
                }
            }
        }
        return true;
    }
}

2315. 统计星号

难度:简单

题目:给你一个字符串 s ,每 两个 连续竖线 '|'一对 。换言之,第一个和第二个 '|' 为一对,第三个和第四个 '|' 为一对,以此类推。

请你返回 不在 竖线对之间,s'*' 的数目。

注意,每个竖线 '|' 都会 恰好 属于一个对。

提示:

  • 1 <= s.length <= 1000
  • s 只包含小写英文字母,竖线 '|' 和星号 '*'
  • s 包含 偶数 个竖线 '|'

模拟

  1. 从左向右遍历,如果找到奇数个竖线,则接下来找到的星号不计数,找到偶数个竖线,则接下来的星号计数
  2. 第一个竖线前的星号计数,后面通过找到竖线的奇偶性,变动计数开关,来达到只统计竖线外星号的目的。初始计数数目为 cnt=1,每次遇到一个竖线后,个位取反,可以用异或:cnt = cnt^1 或者直接减:cnt=1-cnt 的方式来变动计数开关
  3. 遇到星号时,直接加计数开关即可
class Solution {
    public int countAsterisks(String s) {
        int cnt = 1;
        int n = s.length();
        int ans = 0;
        for(int i = 0;i < n;i++){
            char c = s.charAt(i);
            if(c == '|' ){
                cnt = 1-cnt;
            }else if(c == '*'){
                ans += cnt;
            }       
        }
        return ans;
    }
}

三、动态规划

剑指 Offer 47. 礼物的最大价值

难度:中等

题目:在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

提示:

  • 0 < grid.length <= 200
  • 0 < grid[0].length <= 200

思路:

题意:从棋盘左上角,每次向右或向下移动一格,直到棋盘右下角,求可获得的最大价值。

设f(i,j) 代表从左上角到达单元格 (i,j) 时,可拿的最大累计价值;gird(i-1, j-1) 为上一单元格价值。

dp 方程为:f(i,j) = Math.max(f(i-1, j), f(i, j-1)) + grid(i-1,j-1);

解题代码:

class Solution {
    public int maxValue(int[][] grid) {
        // 初始化棋盘行列
        int row = grid.length;
        int column = grid[0].length;
        // dp[i][j] 表示从 grid[0][0] 到grid[i-1][j-1]时的最大价值
        int[][] dp = new int[row + 1][column + 1];
        // 从[1,1]开始遍历,累加上初始价值大小
        for (int i = 1; i <= row; i++) {
            for (int j = 1;j <= column; j++) {
                // dp 方程
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1];
            }
        }
        // 因为row和column为棋盘行和列,所以可为数组的length不越界。
        return dp[row][column];
    }
}

5. 最长回文子串

难度:中等

题目:给你一个字符串 s,找到 s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

解题代码:

class Solution {
    public String longestPalindrome(String s) {
        //动态规划
        if (s == null || s.length() < 2) {
            return s;
        }
        int strLen = s.length();
        int maxStart = 0;  //最长回文串的起点
        int maxEnd = 0;    //最长回文串的终点
        int maxLen = 1;  //最长回文串的长度

        boolean[][] dp = new boolean[strLen][strLen];

        for (int r = 1; r < strLen; r++) {
            for (int l = 0; l < r; l++) {
                if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1])) {
                    // 初始状态,1=r时,此时 dp[1][r] = true
                    dp[l][r] = true;
                    // 状态转移方程,dp[1][r] = true 并且(1-1)和(r+1)两个位置为相同的字符,此时 dp[1-1][r+1] = true.
                    if (r - l + 1 > maxLen) {
                        maxLen = r - l + 1;
                        maxStart = l;
                        maxEnd = r;

                    }
                }

            }

        }
        return s.substring(maxStart, maxEnd + 1);
    }
}

拓展:中心扩展算法

class Solution {
    public String longestPalindrome(String s) {
        //中心扩展算法
       if (s==null ||s.length()==0){return "";}
        //获取字符串长度 用于循环
        int length = s.length();

        //变量存储 i 的下标
        int left=0;
        int right=0;
        // 最大长度,只有一个时为1 ,所以初始值为1;
        int maxlen=0;
        //获取切割字段的起始位置
        int maxStart=0;
        for (int i =0 ; i<length;i++){
            //记录循环长度 ,自动刷新
            int len =1;
          //获取当前位置的左右位置的下标
            left = i-1;
            right= i+1;

            //左扩散
            while (left>=0 && s.charAt(left)==s.charAt(i)){
                len++; left--;
            }
            //右扩散  最后一位也算入
            while (right<length && s.charAt(right)==s.charAt(i)){
                len++; right++;
            }
            //左右扩散 ,左边的值等于右边
            while (right<length && left>=0  && s.charAt(right)==s.charAt(left)){
                len+=2; right++; left--;
            }
            if (len>maxlen){
                maxlen=len;
                //因为每完成一次 ,left--,所以最后合格的一次也--,导致开始位置向后移动一位
                maxStart = left+1;
            }
        }
        return s.substring(maxStart,maxlen+maxStart);
    }
}

474. 一和零

难度:中等

题目:给你一个二进制字符串数组 strs 和两个整数 mn

请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y子集

提示:

  • 1 <= strs.length <= 600
  • 1 <= strs[i].length <= 100
  • strs[i] 仅由 '0''1' 组成
  • 1 <= m, n <= 100

解题代码:

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        //动态规划
        //确定dp数组(dp table)以及下标的含义  dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]
        //确定递推公式  dp[i][j]就是dp[i - zeroNum][j - oneNum] + 1 : dp[i][j] = Math.max(dp[i][j],dp[j - weight[i] + value[i]]);
        //dp数组如何初始化,初始为0
        //确定遍历顺序   遍历背包容量且从后向前的遍历!
        int[][] dp = new int[m+1][n+1];
        dp[0][0] = 0;
        for (String str:strs) { //遍历物品
            int[] zeroAndOne = calcZeroAndOne(str);
           int zeroNum = zeroAndOne[0];
           int oneNum = zeroAndOne[1];
            for (int i = m;i >= zeroNum;i--) {//遍历背包容量且从后向前遍历
                for (int j = n;j >= oneNum;j--){
                    dp[i][j] = Math.max(dp[i][j],dp[i - zeroNum][j - oneNum] + 1);
                }
            }
        }
        return dp[m][n];
    }
    private static int[] calcZeroAndOne(String str) {
        int[] res = new int[2];
        for (char c : str.toCharArray()) {
            res[c - '0']++;
        }
        return res;
    }
}

494. 目标和

难度:中等

题目:给你一个整数数组 nums 和一个整数 target

向数组中的每个整数前添加 '+''-' ,然后串联起所有整数,可以构造一个 表达式

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1"

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

提示:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 1000
  • 0 <= sum(nums[i]) <= 1000
  • -1000 <= target <= 1000

解题代码(动态规划):

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0;
        //统计整数数组中所有数的和
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
            //绝对值范围超过了sum的绝对值范围则无法得到
        }
        //判断两者的绝对值大小
        if (Math.abs(target) > Math.abs(sum)) {
            return 0;
        }
        int len = nums.length;
        int range = sum * 2 + 1;//因为要包含负数所以要两倍,又要加上0这个中间的那个情况
        int[][] dp = new int[len][range];//这个数组是从总和为-sum开始的
        //加上sum纯粹是因为下标界限问题,赋第二维的值的时候都要加上sum
        // 初始化  第一个只能分别组成+-nums[i]的一种情况
        dp[0][sum + nums[0]] += 1;
        dp[0][sum - nums[0]] += 1;
        for (int i = 1; i < len; i++) {
            for (int j = -sum; j <= sum; j++) {
                if ((j+nums[i]) > sum) {//+不成立  加上当前数大于了 sum   只能减去当前的数
                    dp[i][j+sum] = dp[i - 1][j - nums[i] + sum] + 0;
                }else if((j - nums[i]) < -sum) { //-不成立, 减去当前数小于-sum  只能加上当前的数
                    dp[i][j+sum] = dp[i-1][j+nums[i]+sum] + 0;
                }else { // +-都可以
                    dp[i][j+sum] = dp[i - 1][j + nums[i] + sum] + dp[i-1][j-nums[i] + sum];
                }
            }
        }
        return dp[len - 1][sum + target];
    }
}

解题代码(01背包):

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        /*类01背包问题:
        设pos为取+的数字和,neg为取-的数字和(均为正数),则target = pos - neg=pos - (sum - pos) = 2*pos-sum
        故pos=(sum + target)/ 2 >= 0且为常数,因此此问题等价于求有多少种方法用nums[i]凑成和为pos
        进而该问题抽象为:用价值与体积均为nums[i]的物品,恰好凑满容量为pos的背包方案数
        1、动态定义:dp[i]为恰好能凑满容量为j的背包方案数
        2、状态转移:背包容量能或者不能装下nums[i]
            2.1当不能装下nums[i]时,方案数直接继承之前的dp[j]
            2.2当能装下nums[i]时,总的方案数为不考虑nums[i]的方案数 + 有nums[i]参与新增的方案数
                dp[j] += dp[j - nums[i]],dp[j - nums[i]]种方案与nums[i]共同凑成pos,即1*dp[j - nums[i]]
        3、状态初始化:dp[0] = 1,因为后面总会一直查找至j = 0,如dp[3] += dp[3 - 3],空集是任意一条有效路径的起点,当属一条
        4、遍历顺序:i正序,j倒序
        5、返回形式:dp[pos]就是凑成pos总的方案数
         */
        int len = nums.length;
        int sum = 0;
        for (int num : nums) sum += num;
        //pos为小数 || target绝对值比sum还大
        if((sum + target) % 2 == 1 || Math.abs(target) > sum) return 0;
        int pos = (sum + target) / 2;
        int[] dp = new int[pos + 1];
        dp[0] = 1;
        for (int num : nums) {
            for (int j = pos; j >= num; j--) {
                dp[j] += dp[j - num];
            }
        }
        return dp[pos];
    }
}

最后

希望可以点赞❤️+收藏⭐,谢谢各位大佬~~🙌🙌🙌

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乌云暮年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值