【菜鸟刷题笔记——持续更新】

前言
在读上海某211计算机专硕,把平时刷题的笔记整理了一下发布在这里,很多题目是照着代码随想录的刷题路线做的,包括解题思路等等。本人代码基础挺薄弱的,刷的很多题都比较简单,发在博客上督促自己进步,希望与大家共勉。

文章目录

Java数据结构常见操作

List

方法描述
isEmpty()
contains(Object o)
add(index, e)添加元素,默认在末尾,也可通过index指定下标
remove(index)删除指定下标元素,并返回该元素
set(index, e)替换更新
toArray()List转Array
// List to Array
//首先构造一个List的集合
List<Integer> list = new ArrayList<Integer>(){{add(1);add(4);add(7);}};
//使用toArray()方法
Integer[] nums = list.toArray(new Integer[list.size()]);
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");		
String[] nums = list.toArray(new String[list.size()]);		

// Array to List
//首先构造- -个nums的数组
int[] nums={1,4,7}; 
//初始化-个list集合
List<Integer> list = new ArrayList<>();
//使用for循环一个一个加到list集合中
for(int num : nums){
  list.add(num);
}

String

方法描述
chatAt(index)
compareTo(Object)两个字符串按照字典序比较
concat(string)连接字符串
toCharArray()字符串转字符数组
contains()是否包含子串

Map

方法描述
containsKey(Object)bool,返回是否包含key
put(key, val)
remove(k, v)删除键值对,成功返回true
remove(k)删除指定key的键值对,并返回val,不存在返回null
entrySet()返回键值对组成的Set
keySet()返回所有key的set集合
values()返回所有val的集合
get(key)返回指定Key所对应的value,不存在则返回null
HashMap
遍历

通过entrySet()得到所有的键值对集合,通过迭代器iterator进行遍历

public static void main(String[] args) {
        Map<Object, Object> map = new HashMap<>();
        map.put("caocao","11");
        map.put("liubei","22");
        map.put("sunquan","33");
        first(map);

    }

    private static void first(Map<Object, Object> map) {
        Iterator<Map.Entry<Object, Object>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Object, Object> entry = iterator.next();
            System.out.println("key:" + entry.getKey() + ",vaule:" + entry.getValue());
        }
    }

通过entrySet()得到所有的键值对集合,用for循环进行遍历

private static void fourth(Map<Object, Object> map) {
        for (Map.Entry<Object,Object> entry: map.entrySet()) {
            System.out.println("key:" + entry.getKey() + ",vaule:" + entry.getValue());
        }
    }

通过keySet得到所有key的集合,再通过get(key)获得对应的value

private static void second(Map<Object, Object> map) {
        Iterator<Object> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            Object key = iterator.next();
            System.out.println("key:" + key + ",vaule:" + map.get(key));
        }
    }

通过Lambda遍历

private static void seventh(Map<Object, Object> map) {
        map.forEach((key,value) -> {
            System.out.println("key:" + key + ",vaule:" + value);
        });
    }
更新Value值

迭代器iterator

//遍历HashMap
Set<Character> set =map.keySet();
//得到遍历 key 的选代器
Iterator it = set.iterator();		
while(it.hasNext()){
    Character key=(Character) it.next();
    System.out.println("key:"+key+",value:"+map.get(key));
}

Lambda遍历

String s = new String("abcdefg");
HashMap<Character, Integer> map = new HashMap<>();
char[] c = s.toCharArray();
//更新HashMap的value
for(int i = 0; i < c.length; i++) {
// 可以直接调用getOrDefault(key, defaultValue)更新
// map.put(c[i], map.getOrDefault(c[i], 0) + 1);
	if(map.containsKey(c[i])) {
    	int num = map.get(c[i]);
        map.put(c[i], num+1);
        }else {
        	map.put(c[i], 1);
        }
    }
常用方法
functiondescribe
getOrDefault获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
forEach对 hashMap 中的每个映射执行指定的操作
merge添加键值对到 hashMap 中
entrySet返回 hashMap 中所有映射项的集合集合视图
get(Object key)通过元素的key返回value
keySet()返回一个Set包含所有key值,可以用来遍历
entrySet()得到所有key-value集合

回溯(递归)

回溯法解决的问题

  • 组合问题:N个数里面按一定规则找出k个数的集合
    一个集合求组合,需要startIndex;多个集合求组合,且各个集合之间互不影响,不需要startIndex
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,解数独等等

「回溯法解决的问题都可以抽象为树形结构」,是的,我指的是所有回溯法的问题都可以抽象为树形结构!

因为回溯法解决的都是在集合中递归查找子集,「集合的大小就构成了树的宽度,递归的深度,都构成的树的深度」

递归就要有终止条件,所以必然是一颗高度有限的树(N叉树)。

解题模板

void backtracking(参数){
	if (终止条件) {
    存放结果;
    return;	//只取叶子结点时,return,如果其他结点也要取,不加return
	}
	for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
    处理节点;
    backtracking(路径,选择列表); // 递归
    回溯,撤销处理结果
	}
} 
组合问题

【77.组合】

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

示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

「图中可以发现n相当于树的宽度,k相当于树的深度」

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    vector<vector<int>> combine(int n, int k) {
        backtracking(1, k, n);
        return result;
    }
    void backtracking(int index, int k, int n){
        //终止条件
        if(path.size()==k){
            result.push_back(path);
            return;
        }
        //循环
        for(int i=index; i<=n; i++){
            path.push_back(i);
            backtracking(i+1, k, n);
            path.pop_back();
        }
    }
};
class Solution {
    // Java中要用集合做声明
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

    private void backtracking(int n, int k, int index){
        if(path.size() == k){
            result.add(new ArrayList<>(path));
            return;
        }
        for(int i=index; i<=n; i++){
            path.add(i);
            backtracking(n, k, i+1);
            path.removeLast();
        }
    }

    public List<List<Integer>> combine(int n, int k) {
        backtracking(n, k, 1);
        return result;
    }
}
排列问题
image-20220420151218621

只取叶子结点!

需要used数组记录使用过的数字,不需要startIndex记录起始位置

  • 每层都是从0开始搜索而不是startIndex
  • 需要used数组记录path里都放了哪些元素了

used[i-1]==true 说明同一树枝使用过

used[i-1]==false 说明同一树层使用过

如果集合包含重复元素

image-20220420202322881
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    vector<vector<int>> permute(vector<int>& nums) {
        int n = nums.size();
        vector<int> used(n, 0);
        backtracking(nums, used);
        return result;
    }
    //排列问题同样的数字顺序不同也是不同的结果,所以不需要index记录遍历到哪里
    void backtracking(vector<int>& nums, vector<int>& used){
        //终止条件
        if(path.size() == nums.size()){
            result.push_back(path);
            return;
        }
        //遍历
        for(int i=0; i<nums.size(); i++){  
            if(used[i] == 1){
                //说明path中已经有这个元素了
                continue;
            }
            used[i] = 1;
            path.push_back(nums[i]);
            backtracking(nums, used);
            used[i] = 0;
            path.pop_back();
        }
    }
};
棋盘问题(n皇后、数独)

n皇后

约束条件:不能同行、不能同列、不能同斜线

//递归函数:row记录遍历到了第几行
backtracking(int n, int row, vector<string>& chessBoard)

终止条件:递归到最底层即 叶子结点 终止

if(row==n){
	result.push_back(chessBoard);
	return;
}

单层搜索逻辑

for (int col = 0; col < n; col++) {
    if (isValid(row, col, chessboard, n)) { // 验证合法就可以放
        chessboard[row][col] = 'Q'; // 放置皇后
        backtracking(n, row + 1, chessboard);
        chessboard[row][col] = '.'; // 回溯,撤销皇后
    }
}
//isValid 合法判断
bool isValid(int row, int col, vector<string>& chessboard, int n) {
    int count = 0;
    // 检查列
    for (int i = 0; i < row; i++) { // 这是一个剪枝
        if (chessboard[i][col] == 'Q') {
            return false;
        }
    }
    // 检查 45度角是否有皇后
    for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {
        if (chessboard[i][j] == 'Q') {
            return false;
        }
    }
    // 检查 135度角是否有皇后
    for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
        if (chessboard[i][j] == 'Q') {
            return false;
        }
    }
    return true;
}

113.路径总和II

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

// Definition for a binary tree node.
 struct TreeNode {
      int val;
      TreeNode *left;
      TreeNode *right;
      TreeNode() : val(0), left(nullptr), right(nullptr) {}
      TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
      TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
  };

class solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    // 递归函数不需要返回值,因为我们要遍历整个树
    void traversal(treenode* cur, int count) {
        if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为sum的路径
            result.push_back(path);
            return;
        }

        if (!cur->left && !cur->right) return ; // 遇到叶子节点而没有找到合适的边,直接返回

        if (cur->left) { // 左 (空节点不遍历)
            path.push_back(cur->left->val);
            count -= cur->left->val;
            traversal(cur->left, count);    // 递归
            count += cur->left->val;        // 回溯
            path.pop_back();                // 回溯
        }
        if (cur->right) { // 右 (空节点不遍历)
            path.push_back(cur->right->val);
            count -= cur->right->val;
            traversal(cur->right, count);   // 递归
            count += cur->right->val;       // 回溯
            path.pop_back();                // 回溯
        }
        return ;
    }

public:
    vector<vector<int>> pathsum(treenode* root, int sum) {
        result.clear();
        path.clear();
        if (root == null) return result;
        path.push_back(root->val); // 把根节点放进路径
        traversal(root, sum - root->val);
        return result;
    }
};

贪心

122.买卖股票2

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

思路

算出每天的利润序列:(prices[i] - prices[i - 1])…(prices[1] - prices[0])

将所有正利润相加就是最大利润

53.连续子数组的最大和

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n);
        int result = nums[0];
        dp[0] = nums[0];
        for(int i=1; i<n; i++){
            //dp[i]代表以元素 nums[i] 为结尾的连续子数组最大和。
            dp[i] = max(dp[i-1]+nums[i], nums[i]);	//加上某数后还不如某数大,则直接使用某数
            result = max(result, dp[i]); 
        }
        return result;
    }
};

455.分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

动态规划

image-20220502152235012

解决模板

  1. 确定dp数组以及下标的含义
  2. 确定递推公式
  3. dp数组初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

动态规划基础

343.整数拆分

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。返回你可以获得的最大乘积。

示例 1: 输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
说明: 你可以假设 n 不小于 2 且不大于 58。

【解题】

dp[i]含义:正整数i的最大乘积

max中要有dp[i],因为保存了二层循环内的最大值

递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j});

//Cpp
class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n+1);
        //赋初值  dp[i]含义:正整数i的最大乘积
        dp[0] = 0, dp[1] = 1;
        if(n<=1) return dp[n];
        for(int i=2; i<=n; i++){
            //递推
            for(int j=1; j<i; j++){
                dp[i] = max(dp[i], max(dp[i-j]*j, (i-j)*j));
            }
        }
        return dp[n];
    }
};
//Java
class Solution {
    public int integerBreak(int n) {
        //dp[i]:i的最大乘积
        int[] dp = new int[n+1];
        //初始化dp
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 1;
        if(n<=1)    return dp[n];
        //递推:dp[i] = max(dp[i], (i-j)*j, dp[i-j]*j)
        for(int i=3; i<=n; i++){
            for(int j=0; j<i-1; j++){
                dp[i]=Math.max(dp[i], Math.max((i-j)*j, dp[i-j]*j));
            }
        }
        return dp[n];
    }
}
279.完全平方数

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量

class Solution {
    public int numSquares(int n) {
        int [] dp = new int[n+1];
        if(n<2) return n;
        for(int i=1; i<=n; i++){
            //最坏情况 dp[i] = i 全是1相加
            dp[i] = i;
            for(int j=1; i-j*j >= 0; j++){
                //dp[i - j*j]可以遍历出所有包含完全平方的和
                dp[i] = Math.min(dp[i], dp[i-j*j]+1);
            }
        }
        return dp[n];
    }
}
322.零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

class Solution {
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[] dp = new int[amount+1];
        //dp[i]:组成金额i的最少硬币数
        dp[0] = 0;
        for(int i=1; i<amount+1; i++){
            int minCoins = Integer.MAX_VALUE;
            for(int j=0; j<n; j++){
                if(i-coins[j] >= 0 && dp[i-coins[j]] < minCoins){
                    //i-coins[j] >= 0说明当前金额减去当前硬币仍可以
                    //dp[i-coins[j]] < minCoins 说明当前硬币数小于已知的最小硬币数,继续计算是有意义的
                    minCoins = dp[i-coins[j]] + 1;
                }
            }
            dp[i] = minCoins;
        }
        if(dp[amount] == Integer.MAX_VALUE){
            return -1;
        }
        return dp[amount];
    }
}

背包问题

image-20220423222202554
01背包⭐⭐

【问题描述】:有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

【例题】背包最大重量为4

重量价值
物品0115
物品1320
物品2430
image-20220423222756199

dp含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。本题要求i=2,j=4

递推公式:

  • 由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]
  • 由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值

初始化:dp[i][0]=0;

// dp[0][j] 要倒叙遍历 存放编号0的物品的时候,各个容量的背包所能存放的最大价值。 如果背包重量<0号物品重量,该单元=0
for (int j = bagWeight; j >= weight[0]; j--) {
    dp[0][j] = dp[0][j - weight[0]] + value[0]; // 初始化i为0时候的情况
}

确定遍历顺序:

先遍历物品,然后遍历背包重量

// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
    for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 
        if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组里元素的变化
        else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
    }
}

如何确定遍历顺序?

要理解递归的本质和递推的方向

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 递归公式中可以看出dp[i][j]是靠dp[i-1][j]和dp[i - 1][j - weight[i]]推导出来的。

dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正左和正上两个方向),那么先遍历物品,再遍历背包的过程如图所示:

image-20220423230111708

滚动数组】可以将二维数组优化为一维数组

对于二维数组的递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])

如果把dp[i - 1]那一层拷贝到dp[i]上,表达式可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);

  1. 确定dp数组的定义
    在一维dp数组中,dp[j]表示:容量为j的背包,所背的最大物品价值

  2. 递推公式

    dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    
  3. 初始化
    dp[0]=0

  4. 遍历顺序

    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    
416.分割等和子集

题目描述】给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200

示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for(int i:nums){
            sum+=i;
        }
        //sum/2是背包的容量
        if(sum%2!=0) return false;
        //volume:背包容量
        int volume = sum/2;
        //dp[i]:背包总容量为i,最大可以凑成i的子集总和为dp[i]
        int[] dp = new int[volume+1];
        dp[0] = 0;
        //遍历每个物品
        for(int i=0; i<nums.length; i++){
            for(int j=volume; j>=nums[i]; j--){
                //从后往前 递推
                dp[j] = Math.max(dp[j], dp[j-nums[i]]+nums[i]);
            }
        }
        if(dp[volume]==volume){
            return true;
        }else{
            return false;
        }
    }
}

二维数组版本

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }

        if (sum % 2 == 1)
            return false;
        int target = sum / 2;

        //dp[i][j]代表可装物品为0-i,背包容量为j的情况下,背包内容量的最大价值
        int[][] dp = new int[nums.length][target + 1];

        //初始化,dp[0][j]的最大价值nums[0](if j > weight[i])
        //dp[i][0]均为0,不用初始化
        for (int j = nums[0]; j <= target; j++) {
            dp[0][j] = nums[0];
        }

        //遍历物品,遍历背包
        //递推公式:
        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j <= target; j++) {
                //背包容量可以容纳nums[i]
                if (j >= nums[i]) {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

        return dp[nums.length - 1][target] == target;
    }
}
1049.最后一块石头的重量

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

示例:
输入:[2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。

【思路分析】

尽可能的把石头分成重量相同的两堆,这样碰撞后得到的石头最小

weight:石头重量

value:石头重量

1.确定dp含义
dp[i]:表示容量为i的背包,最多可以背dp[i]重量的石头

2.递推公式

//	dp[j - stones[i]]为 容量为j - stones[i]的背包最大所背重量。
dp[j] = max(dp[j], dp[j - stone[i]] + stone[i]);

3.dp初始化(初值,确定dp数组大小)

既然 dp[j]中的j表示容量,那么最大容量(重量)是多少呢,就是所有石头的重量和。

因为提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000 。

而我们要求的target其实只是最大重量的一半,所以dp数组开到15000大小就可以了。

当然也可以把石头遍历一遍,计算出石头总重量 然后除2,得到dp数组的大小。

target = totalWeight/2

4.确定遍历顺序

for(int i=0; i<n; i++){
    for(int j=totalWeight/2; j>=stones[i]; j--){
        //dp递推
        dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
    }
}
//相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]。
494.目标和

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

输入:nums: [1, 1, 1, 1, 1], S: 3 输出:5 解释:

-1+1+1+1+1 = 3 +1-1+1+1+1 = 3 +1+1-1+1+1 = 3 +1+1+1-1+1 = 3 +1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。

思路分析

sum = nums数组累加;做加法的数总和为x,那么做减法的为sum-x;要让x-(sum-x)=S,则x=(sum+S)/2

问题变为:装满x=(sum+S)/2的背包有几种方法,要注意判断x为奇数的情况

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int n = nums.length;
        int result = 0, sum = 0;
        for(int i:nums){
            sum+=i;
        }
        if(target>sum){
            return 0;
        }
        int volume = (target+sum)/2;
        //dp[i]:运算结果为i的方法有dp[i]种
        int[] dp = new int[volume+1];
        dp[0] = 1;
        //遍历
        for(int i=0; i<n; i++){
            for(int j=volume; j>=nums[i]; j--){
                //递推
                dp[j] += dp[j-nums[i]];
            }
        }
        return dp[volume];
    }
}
完全背包

打家劫舍

打家劫舍1

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

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

房屋围成一个环,即首元素和尾元素是相连的,其余条件不变

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        //要加条件判断 不然会有指针错误
        if(n==0) return 0;
        if(n==1) return nums[0];
        int[] dp = new int[n+1];
        //情况1:含首不含尾
        int rob1 = robRange(nums, 0, n-2);
        //情况2:含尾不含首
        int rob2 = robRange(nums, 1, n-1);
        return Math.max(rob1, rob2);
    }
    //单独把循环遍历写成一个函数
    int robRange(int[] nums, int start, int end){
        if(start==end) return nums[start];
        int[] dp = new int[nums.length];
        dp[start] = nums[start];
        dp[start+1] = Math.max(nums[start], nums[start+1]);
        for(int i=start+2; i<=end; i++){
            dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1]);
        }
        return dp[end];
    }
}

股票系列

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

解题思路

//dp[i]:第i天能获得的最大利润
//递推公式:dp[i] = max(dp[i-1], prices[i]-minCost); 要么之前的利润最大,要么当日股价-最低股价最大
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n<=1) return 0;
        vector<int> dp(n);
        dp[0] = 0;
        int minCost = prices[0];
        for(int i=1; i<n; i++){
            minCost = min(minCost, prices[i]);
            dp[i] = max(dp[i-1], prices[i]-minCost);
        }
        return dp[n-1];
    }
};

子序列系列

300.最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

解题思路

1.dp[i]:0~i区间的最长递增子序列

2.递推公式:

//位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。 需要双层循环
if (nums[i] > nums[j]){
    dp[i] = max(dp[i], dp[j] + 1);
} 

3.初始化:每个dp[i]都至少为1

4.确定遍历顺序

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if(n<2) return n;
        int[] dp = new int[n];
        int result = 0;
        //一定要全部赋初值
        for(int i=0; i<n; i++){
            dp[i]=1;
        }
        for(int i=1; i<n; i++){
            for(int j=0; j<i; j++){
                if(nums[i]>nums[j]){
                    dp[i] = Math.max(dp[i], dp[j]+1);
                }
            }
            result = Math.max(result, dp[i]);
        }
        return result;
    }
}
718.最长重复子数组

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:

输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3

解题思路

题目中的子数组,其实就是连续子序列。这种问题动规最拿手

1.dp[i][j]:以下标i-1结尾的A,以下标j-1结尾的B,最长公共子数组长度为dp[i][j]

2.递推公式:

if(A[i-1]==B[j-1]){
	dp[i][j] = dp[i-1][j-1]+1;
}

3.初始化:从下标为1开始循环遍历,dp[0][0] = 0;首行和首列也都为0

4.确定循环顺序:外层循环A,内层循环B

5.举例

image-20220430205430962
class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int m = nums1.length, n = nums2.length;
        int[][] dp = new int[m+1][n+1];
        dp[0][0] = 0;
        int result = 0;
        for(int i=1; i<=m; i++){
            dp[i][0] = 0;
        }
        for(int j=1; j<=n; j++){
            dp[0][j] = 0;
        }
        //
        for(int i=1; i<=m; i++){
            for(int j=1; j<=n; j++){
                if(nums1[i-1]==nums2[j-1]){
                    dp[i][j] = dp[i-1][j-1]+1;
                }
                result = Math.max(result,dp[i][j]);
            }
        }
        return result;
    }
}
1143.最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

解题思路

1.dp[i][j]:以下标i-1结尾的text1,以下标j-1结尾的text2,最长公共子数组长度为dp[i][j]

2.递推公式:

//如果text1[i-1]和text2[j-1]相同
if(text1[i-1]==text2[j-1]){
	dp[i][j] = dp[i-1][j-1]+1;
} else{
    //如果不相同 取dp[i][j-1]和dp[i-1][j]较大的
    dp[i][j] = max(dp[i][j-1], dp[i-1][j]);
}

3.初始化:从下标为1开始循环遍历,dp[0][0] = 0;首行和首列也都为0

4.确定循环顺序:外层循环text1,内层循环text2

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int n1 = text1.length();
        int n2 = text2.length();
        int result = 0;
        int[][] dp = new int[n1+1][n2+1];
        for(int k=1; k<=n1; k++){
            dp[k][0] = 0;
        }
        for(int k=1; k<=n2; k++){
            dp[0][k] = 0;
        }
        dp[0][0] = 0;
        for(int i=1; i<=n1; i++){
            for(int j=1; j<=n2; j++){
                if(text1.charAt(i-1)==text2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1]+1;
                } else{
                    dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]);
                }
                result = Math.max(result, dp[i][j]);
            }
        }
        return result;
    }
}
53.最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

解题思路

【贪心】

例[-2,1,4,-3,2,-3,5]

局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。

全局最优:选取最大“连续和”

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int result = INT32_MIN;
        int count = 0;
        for (int i = 0; i < nums.size(); i++) {
            count += nums[i];
            if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
                result = count;
            }
            if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
        }
        return result;
    }
};

【动态规划】

1.dp[i]:下标为i之前的最大连续子序和为dp[i]

2.递推公式:

//要么从当前位置重新开始
dp[i] = max(nums[i-1], dp[i-1]+nums[i]);
class Solution {
    //动态规划
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        if(n<1) return 0;
        if(n==1) return nums[0];
        int[] dp = new int[n];
        dp[0] = nums[0];
        //result不能设成0 或者设成min_max
        int result = dp[0];
        //
        for(int i=1; i<n; i++){
            dp[i] = Math.max(nums[i], dp[i-1]+nums[i]);
            result = Math.max(result, dp[i]);
        }
        return result;
    }
}
392.判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

双指针法

class Solution {
public:
    bool isSubsequence(string s, string t) {
        bool flag = false;
        int m = s.size();
        int n = t.size();
        int count = 0;
        if(m>n) return false;
        if(m==0) return true;
        //range
        int i=0, j=0;
        //注意:双指针用while循环较方便
        while(i<m&&j<n){
            if(s[i]==t[j]){
                i++;
                j++;
                count++;
            } else{
                j++;
            }
            if(count==m)  flag=true;
        }
        return flag;
    }
};

动态规划

1.dp[i][j]:表示以下标i-1结尾的字符串s,和以下标j-1结尾的字符串t,相同子序列的长度为dp[i][j]

2.递推公式:

if(s[i-1]==t[j-1]){
	dp[i][j] = dp[i-1][j-1]+1;
} else{
	//不一样 则相当于t要删除掉当前指向的元素
	dp[i][j] = dp[i][j-1];
}

3.初始化

image-20220502160029827
class Solution {
	public:
		bool isSubsequence(string s, string t) {
			vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
            for (int i = 1; i <= s.size(); i++) {
                for (int j = 1; j <= t.size(); j++) {
                if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = dp[i][j - 1];
                }
            }
            if (dp[s.size()][t.size()] == s.size()) return true;
            return false;
        }
};
72.编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)

解题思路

1.dp[i][j]: 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j],编辑距离就是最少操作数。

2.递推公式:

操作一(增):word1增加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 加上一个增加元素的操作。

即 dp[i][j] = dp[i - 1][j] + 1;

操作二(删):word2添加一个元素,使其word1[i - 1]与word2[j - 1]相同,那么就是以下标i-1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个增加元素的操作。

即 dp[i][j] = dp[i][j - 1] + 1;

这里有同学发现了,怎么都是添加元素,删除元素去哪了。

word2添加一个元素,相当于word1删除一个元素,例如 word1 = “ad” ,word2 = “a”,word2添加一个元素d,也就是相当于word1删除一个元素d,操作数是一样!

操作三(换):替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增加元素,那么以下标i-2为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个替换元素的操作。

即 dp[i][j] = dp[i - 1][j - 1] + 1;

综合三种操作:

dp[i][j] = min(dp[i-1][j]+1, min(dp[i-1][j-1]+1, dp[i][j-1]+1));

3.初始化:dp[0][0] = 0;dp[i][0] = i;dp[0][j] = j

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.size();
        int len2 = word2.size();
        int result=0;
        vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
        //初始化
        for(int k=0; k<=len1; k++){
            dp[k][0] = k;
        }
        for(int k=0; k<=len2; k++){
            dp[0][k] = k;
        }
        //range
        for(int i=1; i<=len1; i++){
            for(int j=1; j<=len2; j++){
                if(word1[i-1]==word2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                } else{
                    dp[i][j] = min(dp[i-1][j]+1, min(dp[i-1][j-1]+1, dp[i][j-1]+1));
                }
            }
        }
        result = dp[len1][len2];
        return result;
    }
};
647.回文子串

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

双指针——中心扩展

例如字符串:ababa

以中间的a为中心点,两个指针left和right分别向两边扩散,每次扩散时判断两指针所指字符是否一致

两个字符的中心点ba,分别向两边扩散

中心点如何确定? len个单字符中心点和len-1个双字符中心点,共有2len-1个中心点

  • aba 有5个中心点,分别是 a、b、a、ab、ba

  • abba 有7个中心点,分别是 a、b、b、a、ab、bb、ba|
    image-20220504135137008

    left = i/2;right=left+i%2

class Solution6472 {
    public int countSubstrings(String s) {
        // 中心扩展法
        int ans = 0;
        for (int center = 0; center < 2 * s.length() - 1; center++) {
            // left和right指针和中心点的关系是?
            // 首先是left,有一个很明显的2倍关系的存在,其次是right,可能和left指向同一个(偶数时),也可能往后移动一个(奇数)
            // 大致的关系出来了,可以选择带两个特殊例子进去看看是否满足。
            int left = center / 2;
            int right = left + center % 2;

            while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
                ans++;
                left--;
                right++;
            }
        }
        return ans;
    }
}

5.最长回文子串⭐

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

动态规划

dp[i][j]:s[i…j]是否为回文子串,bool类型

  • 状态转移方程:
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
// 特殊情况
//如果去掉 s[i..j] 头尾两个字符子串 s[i + 1..j - 1] 的长度严格小于 22(不构成区间),即 j−1−(i+1)+1<2 时,整理得 j - i < 3,此时 s[i..j] 是否是回文只取决于 s[i] 与 s[j] 是否相等
  • 初始化:
dp[i][i] = true
  • 代码实现:
class Solution {
    public String longestPalindrome(String s) {
        int n = s.length();
        int begin = 0;
        int maxLen = 1;
        if(n<2){
            return s;
        }
        boolean[][] dp = new boolean[n][n];
        // 字符串转字符数组 c++可直接取
        char[] charArray = s.toCharArray();
        //初始化
        for(int i=0; i<n; i++){
            dp[i][i] = true;
        }
        //
        for(int j=1; j<n; j++){
            for(int i=0; i<j; i++){
                if(charArray[i] != charArray[j]){
                    dp[i][j] = false;
                }else{
                    if(j-i < 3){
                        //此时是否回文只取决于首尾相等
                        dp[i][j] = true;
                    } else{
                        //取决于内侧子串是否回文
                        dp[i][j] = dp[i+1][j-1];
                    }
                }
                // 只要 dp[i][j] == true 成立,就表示子串 s[i..j] 是回文,此时记录回文长度和起始位置
                if (dp[i][j] == true && j-i+1 > maxLen) {
                    //	这里的j-i+1 > maxLen 条件不能少,否则begin = i会产生误判
                    maxLen = Math.max(j-i+1, maxLen);
                    begin = i;
                }
            }
        }
        return s.substring(begin, begin+maxLen);
    }
}

中心扩散法

遍历每一个下标,以这个下标为中心,利用「回文串」中心对称的特点,往两边扩散,看最多能扩散多远。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VdudB1ki-1673957815957)(C:\Users\11041\AppData\Roaming\Typora\typora-user-images\image-20220825232841331.png)]

public class Solution {

    public String longestPalindrome(String s) {
        int len = s.length();
        if(len < 2) return s;
        
        int maxLen = 0;
        // 数组第一位记录起始位置,第二位记录长度
        int[] res = new int[2];
        for (int i = 0; i < s.length() - 1; i++) {
            int[] odd = centerSpread(s, i, i);
            int[] even = centerSpread(s, i, i + 1);
            int[] max = odd[1] > even[1] ? odd : even;
            if (max[1] > maxLen) {
                res = max;
                maxLen = max[1];
            }
        }
        return s.substring(res[0], res[0] + res[1]);
    }

    private int[] centerSpread(String s, int left, int right) {
        int len = s.length();
        while (left >= 0 && right < len) {
            if (s.charAt(left) == s.charAt(right)) {
                left--;
                right++;
            } else {
                break;
            }
        }
        return new int[]{left + 1, right - left - 1};
    }
}
516.最长回文子序列

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

class Solution {
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        int[][] dp = new int[n][n];
        //初始化
        for(int i=0; i<n; i++){
            dp[i][i] = 1;
        }
        //转成字符数组便于操作
        char[] charArray = s.toCharArray();
        for(int j=0; j<n; j++){
            //注意循环的顺序
            for(int i=j-1; i>=0; i--){
                if(charArray[i] != charArray[j]){
                    //首尾不等
                    dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
                }else{
                    dp[i][j] = dp[i+1][j-1]+2;
                }
            }
        }
        
        return dp[0][n - 1];
    }
}

剑指offer

09.两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTaildeleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。

解题思路

image-20220506143253412

初始栈空,元素入队,依次压入栈A;

元素出队,将栈A元素依次压入栈B,弹出栈顶元素即出队操作;

元素入队,压入栈A,直到栈B因出队操作为空时,再将栈A元素放入栈B中即可。

#include <stack>  
class CQueue {
private:
    stack<int> a, b;

public:
    CQueue() {
    }
    
    void appendTail(int value) {
        a.push(value);
    }
    
    int deleteHead() {
        if(b.empty()){
            if(a.empty()){
                return -1;
            }
            while(!a.empty()){
                b.push(a.top());
                a.pop();
            }
        }
        int result = b.top();
        b.pop();
        return result;
    }
};

30.包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1).

解题思路

加入一个辅助栈,用来记录每个元素入栈时对应的最小值。

image-20220506220218871
class MinStack {
private:
    stack<int> s;
    stack<int> minStack;

public:
    /** initialize your data structure here. */
    MinStack() {
        //这里写的是初始化代码
        minStack.push(INT_MAX);
    }
    
    void push(int x) {
        s.push(x);
        minStack.push(::min(minStack.top(),x));
    }
    
    void pop() {
        s.pop();
        minStack.pop();
    }
    
    int top() {
        return s.top();
    }
    
    int min() {
        return minStack.top();
    }
};

51.数组中的逆序对(归并)

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

解题思路

「归并排序」与「逆序对」是息息相关的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y8jqVyZq-1673957815959)(C:\Users\11041\AppData\Roaming\Typora\typora-user-images\image-20220506233104575.png)]

合并阶段 本质上是 合并两个排序数组 的过程,而每当遇到 左子数组当前元素 > 右子数组当前元素 时,意味着 「左子数组当前元素 至 末尾元素」 与 「右子数组当前元素」 构成了若干 「逆序对」 。

merge_sort() 归并排序与逆序对统计:

1.终止条件: 当 l≥r 时,代表子数组长度为 1 ,此时终止划分;
2.递归划分: 计算数组中点 m ,递归划分左子数组 merge_sort(l, m) 和右子数组 merge_sort(m + 1, r) ;
3.合并与逆序对统计:
[1]暂存数组 nums 闭区间 [i,r] 内的元素至辅助数组tmp ;
[2]循环合并: 设置双指针 i , j 分别指向左 / 右子数组的首元素;
a>当 i = m + 1 时: 代表左子数组已合并完,因此添加右子数组当前元素 tmp[j] ,并执行j=j+1 ;
b>否则,当 j = r + 1 时: 代表右子数组已合并完,因此添加左子数组当前元素 tmp[i] ,并执行i=i+1 ;
c>否则,当 tmp[i]≤tmp[j] 时: 添加左子数组当前元素 tmp[i] ,并执行 i = i + 1;
d>否则(即tmp[i]>tmp[j])时: 添加右子数组当前元素 tmp[j] ,并执行j=j+1 ;此时构成m−i+1 个「逆序对」,统计添加至 res;
4.返回值: 返回直至目前的逆序对总数 res;

int mergeSort(int l, int r, vector<int>& nums, vector<int>& tmp) {
        // 终止条件
        if (l >= r) return 0;
        // 递归划分
        int m = (l + r) / 2;
        int res = mergeSort(l, m, nums, tmp) + mergeSort(m + 1, r, nums, tmp);
        // 合并阶段
        int i = l, j = m + 1;
        for (int k = l; k <= r; k++)
            tmp[k] = nums[k];
        for (int k = l; k <= r; k++) {
            if (i == m + 1)
                nums[k] = tmp[j++];
            else if (j == r + 1 || tmp[i] <= tmp[j])
                nums[k] = tmp[i++];
            else {
                nums[k] = tmp[j++];
                res += m - i + 1; // 统计逆序对
            }
        }
        return res;
    }

04.二维数组中的查找(二叉树)

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

示例Matrix:

[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]

解题思路

将矩阵转化为图,旋转45°后类似二叉搜索树,对于每个结点,其左边元素更小,右边元素更大。

Picture1.png

“根节点” 对应的是矩阵的 “左下角” 和 “右上角” 元素,称之为 标志数 ,以 matrix 中的 左下角元素 为标志数 flag ,这里为3,则有:

  1. 若 flag > target ,则 target 一定在 flag 所在 行的上方 ,即 flag 所在行可被消去。

  2. 若 flag < target ,则 target 一定在 flag 所在 列的右方 ,即 flag 所在列可被消去。

算法流程:
从矩阵 matrix 左下角元素(索引设为 (i, j) )开始遍历,并与目标值对比:
当 matrix[i][j] > target 时,执行 i-- ,即消去第 i 行元素;
当 matrix[i][j] < target 时,执行 j++ ,即消去第 j 列元素;
当 matrix[i][j] = target 时,返回 true ,代表找到目标值。
若行索引或列索引越界,则代表矩阵中无目标值,返回false 。

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        //m行n列
        int i = matrix.size()-1, j=0;
        while(i>=0 && j<matrix[0].size()){
            if(matrix[i][j] > target){
                //消除第i行元素
                i--;
            } else if(matrix[i][j]<target){
                //消除第j列元素
                j++;
            } else{
                return true;
            }
        }
        return false;
    }
};

29.顺时针打印矩阵

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

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

1.初始化:left, right, bottom, top为上下左右四个边界

2.循环打印:

image-20220508162509889
class Solution {
    public int[] spiralOrder(int[][] matrix) {
        if(matrix.length == 0) return new int[0];
        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]; // left to right.
            if(++t > b) break;
            for(int i = t; i <= b; i++) res[x++] = matrix[i][r]; // top to bottom.
            if(l > --r) break;
            for(int i = r; i >= l; i--) res[x++] = matrix[b][i]; // right to left.
            if(t > --b) break;
            for(int i = b; i >= t; i--) res[x++] = matrix[i][l]; // bottom to top.
            if(++l > r) break;
        }
        return res;
    }
}

25.合并两个有序链表

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        //记录合并链表的起点
        ListNode* preHead = new ListNode(-1);
        //记录合并链表当前指针
        ListNode* preNode = preHead;
        while(l1!=nullptr && l2!=nullptr){
            if(l1->val < l2->val){
                preNode->next = l1;
                l1 = l1->next;
            } else{
                preNode->next = l2;
                l2 = l2->next;
            }
            preNode = preNode->next;
        }
        preNode->next = (l1==nullptr) ? l2: l1;
        return preHead->next;
    }
};

45.把数组排成最小的数

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

解题思路

Picture1.png
class Solution {
public:
    string minNumber(vector<int>& nums) {
        int n = nums.size();
        vector<string> str;
        string result = "";
        for(int i:nums){
            str.push_back(to_string(i));
        }
        sort(str.begin(), str.end(), 
        //自定义比较函数⭐
        [](string& x, string& y){ return x + y < y + x; });
        for(string i:str){
            result += i;
        }
        return result;
    }
};

48.最长不含重复字符的子字符串(滑动窗口)

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

动态规划

1.dp[i]:以字符s[j]结尾的最长不重复子串长度为dp[i]

2.递推公式

image-20220510132031088
dp[j] = max(j-i, dp[j-1]+1);
class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> dic = new HashMap<>();
        int res = 0, tmp = 0;
        for(int j = 0; j < s.length(); j++) {
        // 当map有这个key时,返回其对应的value,否则使用默认值
            int i = dic.getOrDefault(s.charAt(j), -1); // 获取索引 i
            // charAt:索引j位置的字符
            dic.put(s.charAt(j), j); // 更新哈希表
            tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j]
            res = Math.max(res, tmp); // max(dp[j - 1], dp[j])
        }
        return res;
    }
}

【滑动窗口双指针】

1.窗口头尾指针head,tail

image-20220510133559080

2.tail指针右移,判断tail所指元素是否在窗口[head, tail]内。如果不在,将该元素加入窗口,更新最大值,tail继续右移;

如果在,将head指针右移,知道窗口不包含该元素

优化

用哈希表代替窗口,不需要在窗口里再进行一层循环

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n=s.size();
        int head=0, result=0;
        unordered_map<char, int> dic;
        for(int i=0; i<n; i++){
            if(dic.find(s[i])!=dic.end()){
                //说明出现过 i相当于tail
                head = max(dic[s[i]], head);
            }
            dic[s[i]] = i+1;
            result = max(result, i-head+1);
        }
        return result;
    }
};

Java代码

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        if(n==0)    return 0;
        Set<Character> occ = new HashSet<Character>();
        int result = 1;
        // 记录当前最长字符数
        int count = 0;
        for(int i=0; i<n; i++){
            for(int j=i; j<n; j++){
                if(!occ.contains(s.charAt(j))){
                    //set中没出现过
                    occ.add(s.charAt(j));
                    count++;
                }else{
                    result = Math.max(result, count);
                    occ.clear();
                    count = 0;
                    break;
                }
            }
        }
        return result;
    }
}

13.机器人的运动范围(模拟遍历)

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

解题思路

同一斜线上的方格其坐标数位和相同,因此在移动的时候只需要向右向下即可

class Solution {
public:
    int get(int x) {
        int res=0;
        for (; x; x /= 10){
            res += x % 10;
        }
        return res;
    }
    int movingCount(int m, int n, int k) {
        //m行n列
        vector<vector<int>> dp(m, vector<int> (n,0));
        int result = 1;
        dp[0][0]=1;
        for(int i=0; i<m; i++){
            for(int j=0; j<n; j++){
                if((i==0 && j==0) || get(i)+get(j)>k){
                    continue;
                }
                if(i-1>=0){
                    //0|0=0;   0|1=1;   1|0=1;    1|1=1;
                    dp[i][j] |= dp[i-1][j];
                }
                if(j-1>=0){
                    dp[i][j] |= dp[i][j-1];
                }
                result += dp[i][j];
            }
        }
        return result;
    }
};

07.重建二叉树(递归)

已知二叉树的前序遍历和中序遍历,求二叉树

解题思路

可以根据中序遍历不断地将二叉树划分为左右子树,再分别对左右子树进行递推

递推函数参数:根节点在前序遍历的索引 root 、子树在中序遍历的左边界 left 、子树在中序遍历的右边界 right

递推终止条件left>right时,递推终止

递推流程

  • 建立根结点

  • 划分左右子树,查找根结点在中序遍历的索引i,这里可用hashMap进行存储

  • 左子树递归 backtracking(root+1, left, i-1);右子树递归 backtracking(i-left+root+1, i+1, right)

  • 返回根结点

class Solution {
public:
    //用来储存先序遍历
    vector<int> preorder;
    //用来标记中序遍历在先序遍历中的索引
    unordered_map<int, int> dic;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        //记录索引
        this->preorder = preorder;
        for(int i=0; i<inorder.size(); i++){
            dic[inorder[i]] = i;
        }
        return backtracking(0, 0, inorder.size()-1);
    }
    TreeNode* backtracking(int root, int left, int right){
        //终止条件
        if(left>right)  return nullptr;
        //node指向当前根结点
        TreeNode* node = new TreeNode(preorder[root]);
        //根结点在先序的位置,找到当前结点在前序中划分左右子树的索引
        int i = dic[preorder[root]];
        //左子树的根结点:root+1,右边界是划分结点
        node->left = backtracking(root+1, left, i-1);
        //右子树的根,就是右子树(前序遍历)的第一个,就是当前根节点 加上左子树的数量
        //左子树的长度 = 左子树的左边-右边 (i-1 - left +1)
        node->right = backtracking(i-left+root+1, i+1, right);
        return node;
    }
};

36.二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

img img

解题思路

1.排序通过中序遍历(左→根→右),可得到由小到大的排序

void midRange(TreeNode* head){
	if(head==nullptr)	return;
	//左
	midRange(head->left);
	//根
	cout<<head->val<<endl;
	//右
	midRange(head->right);
}

2.双向链表

pre.right = cur;
cur.left = pre;

3.循环链表

head.left = tail;
tail.right = head;

因此,按照中序遍历各个结点,遍历时构造双向链表即可

class Solution {
public:
    Node *pre, *head;
    Node* treeToDoublyList(Node* root) {
        if(root==nullptr)   return nullptr;
        midRange(root);
        //头尾结点互指
        head->left = pre;
        pre->right = head;
        return head;
    }
    //中序遍历
    void midRange(Node* root){
        if(root==nullptr)	return;
        //左
        midRange(root->left);
        //对根的处理
        if(pre!=nullptr){
            pre->right = root;
        } else{
            //pre为空
            head = root;
        }
        root->left = pre;
        pre = root;
        //右
        midRange(root->right);
    }
};

35.复杂链表的复制(回溯)

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。复杂链表结构如下图。

img

解题思路

深拷贝(deep copy):浅拷贝只是复制了指针,仍然共用一块内存地址,深拷贝则是新分配一块内存地址,将数据进行复制。

利用哈希表的查询特点,考虑构建 原链表节点新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 nextrandom 引用指向即可。

1.初始化哈希表dic,节点cur指向头节点

2.复制链表,建立新节点,并向 dic 添加键值对 (原 cur 节点, 新 cur 节点)cur 遍历至原链表下一节点;

3.构建新链表的引用指向

4.返回新链表的头节点dic[cur]

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr) return nullptr;
        Node* cur = head;
        unordered_map<Node*, Node*> map;
        // 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
        while(cur != nullptr) {
            map[cur] = new Node(cur->val);
            cur = cur->next;
        }
        cur = head;
        // 4. 构建新链表的 next 和 random 指向
        while(cur != nullptr) {
            map[cur]->next = map[cur->next];
            map[cur]->random = map[cur->random];
            cur = cur->next;
        }
        // 5. 返回新链表的头节点
        return map[head];
    }
};

61.扑克牌中的顺子

从若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。

解题思路

1.不存在重复数字

2.最大-最小<5

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int count0 = 0;
        for(int i=0; i<5; i++){
            if(nums[i]==0){
                count0++;
            }
            if(i>0 && nums[i]!=0 && nums[i]==nums[i-1]){
                return false;
            }
        }
        if(nums[4]-nums[count0]<5){
            return true;
        }
        return false;
    }
};

49.丑数(动态规划)

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

解题思路

dp[i]:第i个丑数,显然,dp[0]=1

一个丑数应该由较小的丑数 ×2 或者 ×3 或者 ×5 所得到

设置索引a, b, c初值均为0,分别记录上一个 ×2 或者 ×3 或者 ×5 所在位置

dp[i] = min(dp[a]*2, dp[b]*3, dp[c]*5);
class Solution {
public:
    int nthUglyNumber(int n) {
        int a=0, b=0, c=0;
        vector<int> dp(n);
        if(n==1) return 1;
        dp[0] = 1;
        for(int i=1; i<n; i++){
            dp[i] = min(dp[a]*2, min(dp[b]*3, dp[c]*5));
            if(dp[i] == dp[a]*2)    a++;
            if(dp[i] == dp[b]*3)    b++;
            if(dp[i] == dp[c]*5)    c++;
        }
        return dp[n-1];
    }
};

46.把数字翻译成字符串(动态规划)

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

解题思路

Picture1.png

dp[i]:0~i-1区间内翻译方法为dp[i],其中dp[0]=1,dp[1]=1

递推公式:

//可被翻译的两位数区间[10, 25]
dp[i] = dp[i-1]+dp[i-2] //该区间内
dp[i] = dp[i-1] //该区间外
class Solution {
public:
    int translateNum(int num) {
        string s = to_string(num);
        int n = s.size();
        vector<int> dp(n+1);
        dp[0] = 1, dp[1] = 1;
        for(int i=2; i<=n; i++){
            //c++的string 2代表长度 java的string代表区间
            string temp = s.substr(i-2, 2);
            if(temp>="10" && temp<="25"){
                dp[i] = dp[i-1] + dp[i-2];
            } else{
                dp[i] = dp[i-1];
            }
        }
        return dp[n];
    }
};

33.二叉搜索树的后续遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

解题思路

后序遍历最后一位是根节点,以下图为例,其后序遍历为[3,5,4,10,12,9]

image.png

找到9后,从前向后遍历,第一个比9大的元素是10,则10,12是9的右子树,3,5,4是9的左子树

class Solution {
public:
    bool verifyPostorder(vector<int>& postorder) {
        //后序:左右根 小大中
        return backtracking(postorder, 0, postorder.size()-1);
    }
    bool backtracking(vector<int>& postorder, int left, int right){
        //left right代表数组左右区间 postorder[right]是根结点
        if(left>=right) return true;
        int mid = left;
        int root = postorder[right];
        while(postorder[mid] < root){
            //
            mid++;
        }
        //temp标记了第一个比当前根结点大的值
        int temp = mid;
        while(temp<right){
            //根结点右边的要大于根 否则 false
            if(postorder[temp++] < root){
                return false;
            }
        }
        //继续递归左子树和右子树
        return backtracking(postorder, left, mid-1) && backtracking(postorder, mid, right-1);
    }
};

辅助栈法

[3,5,4,10,12,9]

1.如果挨着的两个数arr[i]<arr[i+1],那么arr[i+1]一定是arr[i]的右子节点

2.如果arr[i]>arr[i+1],那么arr[i+1]一定是arr[0]……arr[i]中某个节点的左子节点,并且这个值是大于arr[i+1]中最小的。

  • 遍历数组的所有元素,如果栈为空,就把当前元素压栈。
  • 如果栈不为空,并且当前元素大于栈顶元素,说明是升序的,那么就说明当前元素是栈顶元素的右子节点,就把当前元素压栈,如果一直升序,就一直压栈。
  • 当前元素小于栈顶元素,说明是倒序的,说明当前元素是某个节点的左子节点,我们目的是要找到这个左子节点的父节点,就让栈顶元素出栈,直到栈为空或者栈顶元素小于当前值为止,其中最后一个出栈的就是当前元素的父节点。
public boolean verifyPostorder(int[] postorder) {
    Stack<Integer> stack = new Stack<>();
    int parent = Integer.MAX_VALUE;
    //注意for循环是倒叙遍历的
    for (int i = postorder.length - 1; i >= 0; i--) {
        int cur = postorder[i];
        //当如果前节点小于栈顶元素,说明栈顶元素和当前值构成了倒叙,
        //说明当前节点是前面某个节点的左子节点,我们要找到他的父节点
        while (!stack.isEmpty() && stack.peek() > cur)
            parent = stack.pop();
        //只要遇到了某一个左子节点,才会执行上面的代码,才会更新parent的值,否则parent就是一个非常大的值,如果一直没有遇到左子节点,那么右子节点可以非常大
        if (cur > parent)
            return false;
        //入栈
        stack.add(cur);
    }
    return true;
}

60.n个骰子的点数(动态规划)

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

解题思路

1.dp[i][j]:投掷i个骰子后,点数j的出现次数

2.递推公式:

投掷完 n 枚骰子后点数 j 出现的次数,可以由投掷完 n−1 枚骰子后,对应点数 j-1, j-2, j-3, … , j-6出现的次数之和转化过来。

for (第n枚骰子的点数 i = 1; i <= 6; i ++) {
    dp[n][j] += dp[n-1][j - i]
}

3.初始化

for(int i=1; i<=6; i++){
	dp[1][i] = 1;
}
class Solution {
public:
    vector<double> twoSum(int n) {
        int dp[15][70];
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= 6; i ++) {
            dp[1][i] = 1;
        }
        for (int i = 2; i <= n; i ++) {
            for (int j = i; j <= 6*i; j ++) {
                for (int cur = 1; cur <= 6; cur ++) {
                    if (j - cur <= 0) {
                        break;
                    }
                    dp[i][j] += dp[i-1][j-cur];
                }
            }
        }
        int all = pow(6, n);
        vector<double> ret;
        for (int i = n; i <= 6 * n; i ++) {
            ret.push_back(dp[n][i] * 1.0 / all);
        }
        return ret;
    }
}; 

TOP100

2.两数相加(链表)

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

例如:

输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

小技巧:对于链表问题,返回结果为头结点时,通常需要先初始化一个预先指针 pre,该指针的下一个节点指向真正的头结点head。使用预先指针的目的在于链表初始化时无可用节点值,而且链表构造过程需要指针移动,进而会导致头指针丢失,无法返回结果。

解题思路

1.按照竖式加法进行模拟,要注意可能产生的进位

2.两个链表长度不同时,可在短链表末尾补零,即高位添零

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int carry = 0; //进位
        ListNode* head = new ListNode(-1); //head->next指向头指针
        ListNode* curNode = head;   //辅助指针
        while(carry||l1||l2){
            int n1 = l1?l1->val:0;
            int n2 = l2?l2->val:0;
            //新建一个结点 存放结果
            //不能 int sum = n1+n2+carry 会存在精度问题!!
            ListNode* node = new ListNode((n1+n2+carry)%10);
            curNode->next = node;
            curNode = node;
            //非空指向next 否则指向空指针
            l1 = l1? l1->next : nullptr;
            l2 = l2? l2->next : nullptr;
            carry = (n1+n2+carry)/10;
        }
        return head->next;
    }
};

合并排序的数组

给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。

初始化 A 和 B 的元素数量分别为 m 和 n。

class Solution {
public:
    void merge(vector<int>& A, int m, vector<int>& B, int n) {
        vector<int> result(m+n);
        //双指针
        int i=0, j=0;
        //应该放入的元素
        int cur;
        while(i<m || j<n){
            //如果一个数组遍历到头,将另一个数组剩下的接上即可
            if(i==m){
                cur = B[j++];
            } else if(j==n){
                cur = A[i++];
            } else if(A[i]<B[j]){
                cur = A[i++];
            } else{
                cur = B[j++];
            }
            result[i+j-1] = cur;
        }
        for(int k=0; k!=m+n; k++){
            A[k] = result[k];
        }
    }
};

21.合并两个有序链表(迭代/回溯)

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

preHead:其next域指向合并后链表的首个结点

解题思路

img
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        //preHead是首结点之前的结点,最后返回的是pre->next
        ListNode* preHead = new ListNode(-1);
        //要指向结点的前一个结点
        ListNode* pre = preHead;
        while(list1!=nullptr && list2!=nullptr){
            if(list1->val > list2->val){
                pre->next = list2;
                list2 = list2->next;
            } else{
                pre->next = list1;
                list1 = list1->next;
            }
            pre = pre->next;
        }
        pre->next = list1==nullptr ? list2 : list1;
        return preHead->next;
    }
};

26.删除有序数组中的重复项

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        int pre = 0, cur = 1;
        while(cur < n){
            if(nums[pre] != nums[cur]){
                // 如果pre和cur相邻则不影响
                nums[pre+1] = nums[cur];
                // 不重复 移动pre
                pre++;
            }
            cur++;
        }
        return pre+1;
    }
}

42.接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

img

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
方法一 按列遍历求

只需要关注正在求的列其左边最高的墙和右边最高的墙,装水的多少取决于这两堵墙较矮的。

1.较矮墙大于当前列的高度

当前列的水 = 较矮墙 - 当前列高

image.png

2.较矮墙小于或等于当前列的高度

不会存在水

image.png

//C++超时	Java不超时
class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        int rain = 0;
        //循环范围[1, n-2]
        for(int i=1; i<n-1; i++){
            //左边最高的墙
            int maxLeft = 0;
            for(int j=0; j<i; j++){
                maxLeft = max(maxLeft, height[j]);
            }
            //右边最高的墙
            int maxRight = 0;
            for(int j=i+1; j<n; j++){
                maxRight = max(maxRight, height[j]);
            }
            //较矮墙大于当前列的高度
            int minWall = min(maxLeft, maxRight);
            if(minWall>height[i]){
                rain = rain + (minWall-height[i]);
            }
        }
        return rain;
    }
};
方法二 动态规划

max_left [i] :代表第 i 列左边最高的墙的高度

//递推
max_left[i] = max(max_left[i-1], height[i-1]);

max_right[i]: 代表第 i 列右边最高的墙的高度

这样应用在方法一中,可以省去一次for循环

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        int rain = 0;
        vector<int> maxLeft(n);
        vector<int> maxRight(n);
        maxLeft[0] = 0, maxRight[0] = 0;
        //循环范围[1, n-2]
        for(int i=1; i<n-1; i++){
            maxLeft[i] = max(maxLeft[i-1], height[i-1]);
        }
        for(int i=n-2; i>=0; i--){
            maxRight[i] = max(maxRight[i+1], height[i+1]);
        }
        for(int i=1; i<n-1; i++){
            int minWall = min(maxLeft[i], maxRight[i]);
            if(minWall>height[i]){
                rain = rain + (minWall-height[i]);
            }
        }
        return rain;
    }
};

15.三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

解题思路

回溯法(超时)
class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<int> used(nums.size(), 0);
        sort(nums.begin(), nums.end());
        backtracking(nums, used, 0, 0);
        set<vector<int>> s;
        for(auto item:result){
            s.insert(item);
        }
        result.assign(s.begin(), s.end());
        return result;
    }
    void backtracking(vector<int>& nums, vector<int>& used, int index, int sum){
        //终止条件
        if(path.size()==3 && sum==0){
            result.push_back(path);
            return;
        }
        //遍历
        for(int i=index; i<nums.size(); i++){
            sum += nums[i];
            used[i] = 1;
            path.push_back(nums[i]);
            backtracking(nums, used, i+1, sum);
            sum -= nums[i];
            used[i] = 0;
            path.pop_back();
        }
    }
};
双指针法(用于消除无效解)

nums进行排序后,指针k指向最左也就是最小的数字,双指针ij分设在数组索引(k,len(nums)) 两端,通过双指针交替向中间移动,记录对于每个固定指针 k 的所有满足nums[k] + nums[i] + nums[j] == 0i,j组合

1.如果nums[k]>0,也就是最小的都比0大,直接break

2.k>0且nums[k]==nums[k-1],此时跳过nums[k],因为会产生重复组合

3.i<j时,循环计算 s = nums[k] + nums[i] + nums[j]

  • 如果s<0,i += 1并跳过所有重复的nums[i](说明三数之和太小,将i右移)

  • 如果s>0,j -= 1并跳过所有重复的nums[j](说明三数之和太大,将j右移)

  • s == 0时,记录组合[k, i, j]res,执行i += 1j -= 1并跳过所有重复的nums[i]nums[j],防止记录到重复组合。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        if(nums.size()<3) return result;
        for(int k=0; k<nums.size()-2; k++){
            // k指向最小的数 所以右侧至少要有两个数
            if(nums[k]>0)   return result;	//最小的数大于0 无解
            if(k>0 && nums[k] == nums[k-1]) continue;	//和前一个数重复 跳过
            int i = k+1, j = nums.size()-1;
            while(i<j){
                int sum = nums[k] + nums[i] + nums[j];
                if(sum<0){
                    i++;
                } else if(sum>0){
                    j--;
                } else{
                    //三数之和等于0
                    result.push_back({nums[k], nums[i], nums[j]});
                    //出现相同数字时
                    while(i<j && nums[j-1]==nums[j]){
                        j--;
                    }
                    while(i<j && nums[i+1]==nums[j]){
                        i++;
                    }
                    //和相同后,i,j指针都移动
                    i++;
                    j--;
                }
            }
        }
        return result;
    }
};

22.括号生成(回溯)

  • 当前左右括号都有大于 0 个可以使用的时候,才产生分支;

  • 产生左分支的时候,只看当前是否还有左括号可以使用;

  • 产生右分支的时候,还受到左分支的限制,右边剩余可以使用的括号数量一定得在严格大于左边剩余的数量的时候,才可以产生分支;

  • 在左边和右边剩余的括号数都等于 0 的时候结算。

class Solution {
public:
    vector<string> result;
    //string path = "";
    vector<string> generateParenthesis(int n) {
        int left = n, right = n;
        backtracking(n, n, "");
        return result;
    }
    void backtracking(int left, int right, string path){
        //终止条件
        if(left==0 && right==0){
            result.push_back(path);
            return;
        }
        //剪枝
        if(left>right) return;
        //遍历
        if(left>0){
            backtracking(left-1, right, path+'(');
        }
        if(right>0){
            backtracking(left, right-1, path+')');
        }
    }
};

11.盛最多水的容器(双指针)

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

解题方法

双循环(超时)
class Solution {
public:
    int maxArea(vector<int>& height) {
        int n = height.size();
        int result = 0;
        for(int i=0; i<n; i++){
            for(int j=n-1; j>i; j--){
                result = max(result, min(height[i], height[j])*(j-i));
            }
        }
        return result;
    }
};
双指针
Picture0.png

无论是移动短板或者长板,我们都只关注移动后的新短板会不会变长,而每次移动的木板都只有三种情况:

1.比原短板短 2.比原短板长 3.与原短板相等;

如向内移动长板,对于新的木板:1.比原短板短,则新短板更短。2.与原短板相等或者比原短板长,则新短板不变。所以,向内移动长板,一定不能使新短板变长。

class Solution {
public:
    int maxArea(vector<int>& height) {
        int n = height.size();
        int result = 0;
        int i=0, j=n-1;
        while(i<j){
            result = max(result, min(height[i], height[j])*(j-i));
            if(height[i]>height[j]){
                j--;
            } else{
                i++;
            }
        }
        return result;
    }
};

9.回文数

将后半段翻折,和前半段比较,注意奇偶区别

  • 每次进行取余操作 ( %10),取出个位的数字:y = x % 10

  • 将最低的数字加到取出数的末尾:revertNum = revertNum * 10 + y

  • 每取一个最低位数字,x 都要自除以 10

  • 判断 x 是不是小于 revertNum ,当它小于的时候,说明数字已经对半或者过半了

  • 最后,判断奇偶数情况:如果是偶数的话,revertNum 和 x 相等;如果是奇数的话,最中间的数字就在revertNum 的最低位上,将它除以 10 以后应该和 x 相等。

class Solution {
    public boolean isPalindrome(int x) {
        //思考:这里大家可以思考一下,末尾为0,开头一定不为0,无法回文
        if (x < 0 || (x % 10 == 0 && x != 0)) return false;
        int revertedNumber = 0;
        while (x > revertedNumber) {
            revertedNumber = revertedNumber * 10 + x % 10;
            x /= 10;
        }
        return x == revertedNumber || x == revertedNumber / 10;
    }
}

字符串

Java String类

方法名描述
int compareTo(String anotherString)比较两个字符串。相等返回0;前大后小返回1;前小后大返回-1
boolean endsWith(String suffix)判断字符串是否以suffix结尾
int indexOf(String str)返回str在字符串第一次出现的位置
int lastIndexOf(String str)返回str最后一次出现的位置
String[] split(String regex)将字符串以regex分割
char[] toCharArray()将字符串转换乘char数组
String substring(int beginIndex, int endIndex)截取beginIndex到endIndex - 1的字符串
String trim()去除字符串两边空格

459.重复的子字符串

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

【解答】

class Solution {
   public boolean repeatedSubstringPattern(String s) {
        String str = s + s;
       // 除去首尾字符 substring(1, str.length() - 1)
        return str.substring(1, str.length() - 1).contains(s);
	}
}

1668.最大重复子字符串

给你一个字符串 sequence ,如果字符串 word 连续重复 k 次形成的字符串是 sequence 的一个子字符串,那么单词 word重复值为 k 。单词 word最大重复值 是单词 wordsequence 中最大的重复值。如果 word 不是 sequence 的子串,那么重复值 k0

给你一个字符串 sequenceword ,请你返回 最大重复值 k

【解答】

dp[i]: 以sequence[i]结尾的最大重复值

如果substring(i-m, i)word相等,那么dp[i] = dp[i-m] + 1

class Solution {
    public int maxRepeating(String sequence, String word) {
        int k = 0;
        int n = sequence.length(), m = word.length();
        if(sequence.indexOf(word) == -1){
            return 0;
        }
        int []dp = new int[n+1];
        // dp[i]: 以sequence[i]结尾的最大重复值
        dp[0] = 0;
        for(int i=1; i<=n; i++){
            if((i-m) < 0)   continue;
            if(sequence.substring(i-m, i).equals(word)){
                dp[i] = dp[i-m] + 1;
            }
            k = Math.max(dp[i], k);
        }
        return k;
    }
}

26.树的子结构(递归)

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

解题思路

1.先序遍历树A的每个结点a

2.判断以a为根节点的子树是否包含树B,该函数记作judge(A, B)

Judge(A, B)

1.终止条件:B为空,匹配成功,返回true;A为空匹配失败,返回false;A!=B,匹配失败,返回false

2.返回值:判断A和B左子结点是否相等,返回judge(A.left, B.left);右子结点同理可推

class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        //1.以 节点 A 为根节点的子树 包含树 BB ,对应 judge(A, B);
		//2.树 B 是 树 A 左子树 的子结构,对应 isSubStructure(A.left, B);
		//3.树 B 是 树 A 右子树 的子结构,对应 isSubStructure(A.right, B);
        if(A==NULL || B==NULL)  return false;
        return judge(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
    }
    bool judge(TreeNode* A, TreeNode* B){
        //终止条件
        if(B==NULL) return true;
        if(A==NULL || A->val!=B->val) return false;
        
        return judge(A->left, B->left) && judge(A->right, B->right);
    }
};

方法:对称性递归⭐⭐

可以用对称性递归解决的二叉树问题大多是判断性问题,对称性递归主要解决以下两类问题:

1.无需辅助函数

  • 单树问题,且不需要用到子树的某一部分(比如根节点左子树的右子树),只要利用根节点左右子树的对称性即可进行递归
  • 双树问题,即本身题目要求比较两棵树,那么不需要构造新函数。

解题模板

1.递归结束条件

  • 单树问题
if(!root->left) return true/false/递归函数;
if(!root->right) return true/false/递归函数;
  • 双树问题
if(!p && !q)return true/false;
if(!p || !q)return true/false;

2.返回值

通常对称性递归的返回值是多个条件的复合判断语句,可能是以下几种条件判断的组合:
1> 节点非空的判断
2> 节点值比较判断
3> (单树)调用根节点左右子树的递归函数进行递归判断
4> (双树)调用两棵树的左右子树的递归函数进行判断

104.二叉树的最大深度(对称性递归)

给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

解题思路

class Solution {
public:
    int maxDepth(TreeNode* root) {
        //空树
        if(root==nullptr)   return 0;
        //+1是根结点的高度
        return max(maxDepth(root->left), maxDepth(root->right))+1;
    }
};

例题

100.相同的树

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(!p && !q)    return true;
        return  
            p && q //该条件需要在最前 首先保证p q不出现一个非空一个空
            && p->val==q->val //值相等
            && (isSameTree(p->left, q->left)) 
            && (isSameTree(p->right, q->right));
    }
};
110.平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。平衡二叉树定义:左右子树最大高度差<=1

class Solution {
private:
    int height(TreeNode* root){
        //以root为根结点的二叉树的最大深度
        if (!root){
            return 0;
        }
        return max(height(root->left), height(root->right)) + 1;
    }
public:
    bool isBalanced(TreeNode* root) {
        if(root == nullptr){
            //空树是平衡二叉树
            return true;
        }
        return abs(height(root->left)-height(root->right))<2 && isBalanced(root->left) && isBalanced(root->right);
    }
};
572.另一个树的子树

判断一个数是不是另一颗树的子树,特殊判断:有一颗树为空就不成立

class Solution {
public:
    //两树是否相同
    bool isSameTree(TreeNode*p, TreeNode*q)
    {
        if (!p && !q)
            return true;
        return p && q && p->val == q->val && (isSameTree(p->left, q->left)) && (isSameTree(p->right, q->right));
    }
    bool isSubtree(TreeNode* root, TreeNode* subRoot) {
        //终止递归的条件
        if(!root || !subRoot)    return false;
        if(isSameTree(root, subRoot)==true) return true;
        //递归 只要左右子树有一个分支成立就可以
        return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
    }
};

2.需要辅助函数

必须要用到子树的某一部分进行递归,即要调用辅助函数比较两个部分子树。形式上主函数参数列表只有一个根节点,辅助函数参数列表有两个节点。

101.对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

**思路:**构造一个辅助函数判断两棵树是否是镜像对称的,然后题目只要判断两棵这个树是否镜像对称
而比较两棵树是否镜像对称,即一棵树的左子树和另一棵树的右子树,以及一棵树的右子树和另一棵树的左子树是否镜像对称

class Solution {
public:
    bool isMirror(TreeNode* p, TreeNode* q){
        //用来判断树p和q是否互为镜像
        if(p==nullptr && q==nullptr)    return true;
        if(!p || !q)    return false;
        return p->val==q->val && isMirror(p->left, q->right) && isMirror(p->right, q->left);
    }
    bool isSymmetric(TreeNode* root) {
        if(!root) return true;
        return isMirror(root->left, root->right);
    }
};

543.二叉树的直径

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

class Solution {
public:
    int maxDepth = 0;
    int depth(TreeNode* node){
        //返回以node为根结点的二叉树的深度
        if(!node) return 0;
        int left = depth(node->left);
        int right = depth(node->right);
        maxDepth = max(left+right, maxDepth);	//存储最大距离
        return max(left, right)+1;  //算上根结点
    }
    int diameterOfBinaryTree(TreeNode* root) {
        depth(root);
        return maxDepth;
    }
};

二叉树的前、中、后序遍历

颜色结点法:

遇到白色结点,变灰色,并倒序入栈(如:中序遍历按照右根左入栈)

遇到灰色结点,输出值

class Solution {
    class ColorNode {
        TreeNode node;
        String color;
        
        public ColorNode(TreeNode node,String color){
            this.node = node;
            this.color = color;
        }
    }
    public List<Integer> inorderTraversal(TreeNode root) {
        if(root == null) return new ArrayList<Integer>();
            
        List<Integer> res = new ArrayList<>();
        Stack<ColorNode> stack = new Stack<>();
        //初始为白色
        stack.push(new ColorNode(root,"white"));
        
        while(!stack.empty()){
            ColorNode cn = stack.pop();
            //白色结点
            if(cn.color.equals("white")){
                if(cn.node.right != null) stack.push(new ColorNode(cn.node.right,"white"));
                stack.push(new ColorNode(cn.node,"gray"));
                if(cn.node.left != null)stack.push(new ColorNode(cn.node.left,"white"));
            }else{
                res.add(cn.node.val);
            }
        }
        
        return res;
    }
}

递归法

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        inorder(root, res);
        return res;
    }

    public void inorder(TreeNode root, List<Integer> res) {
        if (root == null) {
            return;
        }
        inorder(root.left, res);
        res.add(root.val);
        inorder(root.right, res);
    }
}

层序遍历

层序遍历属于广度优先搜索,需要辅助队列

  1. root入队
  2. root出队,其左右孩子入队
  3. 左右孩子出队,各自的左右孩子如果不空继续入队
class Solution {
    
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();
		LinkedList<TreeNode> quene = new LinkedList<TreeNode>();

        if(root==null)  return res;
        
        // root入队
        quene.add(root);
        while(quene.size()>0){
            int n = quene.size();
            ArrayList<Integer> temp = new ArrayList<>();
            for(int i=0; i<n; i++){
                TreeNode tmp = quene.remove();
                temp.add(tmp.val);
                if(tmp.left!=null){
                    quene.add(tmp.left);
                }
                if(tmp.right!=null){
                    quene.add(tmp.right);
                }

            }
            res.add(temp);
        }
        return res;
    }
}

链表

24.两两交换链表的结点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

img

递归法

  1. 返回值:交换完成的子链表
  2. 调用单元:head和next交换,交换后head指向后面的子链表,next指向head
  3. 终止条件:head或next之一为null值
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head==null || head.next==null){
            return head;
        }
        ListNode curNext = head.next;
        // 例如1是要指向子链表交换完成后的结点
        head.next = swapPairs(curNext.next);
        curNext.next = head;
        return curNext;
    }
}

19.删除链表的倒数第N个结点(双指针)

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

img

解题思路

img

设置双指针p, q之间相隔n个元素,则当q指向null时,p恰好在删除元素的前一个

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* preHead = new ListNode(-1);
        preHead->next = head;   //最后返回preHead->next
        ListNode* p=preHead;
        ListNode* q=preHead;
        for(int i=0; i<=n; i++){
            q = q->next;
        }
        while(q!=nullptr){
            p = p->next;
            q = q->next;
        }
        ListNode* delNode = p->next;
        p->next = delNode->next;
        delete delNode;
        return preHead->next;
    }
};
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode preHead = new ListNode(-1);
        // 返回preHead.next 加个头指针
        preHead.next = head;
        ListNode pre = preHead;
        ListNode cur = preHead;

        while(n-->0){
            cur = cur.next;
        }

        while(cur.next!=null){
            cur = cur.next;
            pre = pre.next;
        }

        pre.next = pre.next.next;

        return preHead.next;
    }
}

82.删除排序链表中的重复元素 II

给定一个已排序的链表的头 head删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null)  return head;
		// 如果头结点可能被删除,则需要加一个preHead
        ListNode preHead = new ListNode(-100);
        preHead.next = head;
        ListNode cur = preHead;
        while(cur.next!=null && cur.next.next!=null){
            if(cur.next.val == cur.next.next.val){
                int x = cur.next.val;
                while(cur.next!=null && cur.next.val==x){
                    cur.next = cur.next.next;
                }
            }else{
                cur = cur.next;
            }
        }
        return preHead.next;
    }
}

92.反转链表 II

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

思路

  1. 先将两个指针移动到left前一个和right结点,则可以根据next得到left结点和right后的结点
  2. 截断链表,反转被截断的部分
  3. preLeft接到right,left接到afterRight
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode preHead = new ListNode(-1, head);
        ListNode preL = preHead;
    
        for(int i=0; i<left-1; i++){
            preL = preL.next;
        }
        ListNode rp = preL;
        for(int i=0; i<(right-left+1); i++){
            //循环后rp位于right一个结点
            rp = rp.next;
        }
        
        ListNode leftNode = preL.next;
        ListNode afterRight = rp.next;
        //截断链表
        preL.next = null;
        rp.next = null;
        //调用反转函数
        reverse(leftNode);
        preL.next = rp;
        leftNode.next = afterRight;

        return preHead.next;
    }
	// 这里是将给定头结点的链表完全翻转
    public void reverse(ListNode head){
        ListNode pre = null;
        ListNode cur = head;

        while(cur!=null){
            ListNode after = cur.next;
            cur.next = pre;
            pre = cur;
            cur = after;
        }
    }
    /*******************************************/
    //递归的方式反转全部结点
    public Node reverseList2(Node head) {
        // 递归结束条件
        if (head == null || head.next == null) {
            return head;
        }
		//第一次递归结束,返回的head=4;
        Node newList = reverseList2(head.next);//反转链表
         //返回上层递归 当head.next=3,传入reverseList2时head=3;
        head.next.next = head;
         //此时 head.next.next是4的下一个结点,那么把现在的head=3给head.next.next 即(head.next.next = head) 
        //也就是说明4的下一个结点是3  
        head.next = null;
 
        //将3的下一个结点指向null  即将原来的3与4之间的连接断开
        return newList;
    }
}

花旗

【LeetCode】第1239题——串联字符串的最大长度

class Solution {
    public int maxLength(List<String> arr) {
        List<Integer> list = new ArrayList<>(); // 维护的List列表
        list.add(0); // 先add入一个0,用以把当前字符串对应的二进制数放入List中
        int max = 0; // 取串联字符串的最大长度
        a:for(String s : arr) { // 外层循环,遍历arr
            int bit = 0; // arr[i]的字符串对应的二进制数
            // 这层内循环是确定二进制数
            for(int i = 0; i < s.length(); ++i) {
                int count = s.charAt(i) - 'a';
                if(((bit >> count) & 1) == 1) { // 如果字母重复出现,则可跳过此字符串
                    continue a;
                }
                bit = bit | (1 << count);
            }
            // 这层内循环是将当前字符串对应的二进制数与List中每个二进制数均进行操作
            for(int i = 0; i < list.size(); ++i) {
                int b = list.get(i);
                if((bit & b) != 0) { // 如果不为0,说明有重复字母
                    continue;
                }
                // 如果为0,说明没有重复字母,可以串联
                list.add(bit | b); // 将串联后的字符串所对应的二进制数add入List中
                max = Math.max(max, Integer.bitCount(bit | b)); // 取串联字符串的最大长度
            }
        }
        return max;
    }
}

最短不重复子串

find the length of the shortest unique substring and number of same length unique substring occurring in the string. For eg. “aatcc” will have “t” as the shortest length unique substring and length is 1

def countString(string):
    for i in range(1, len(string)+1): #start the brute force from string length 1

        dictionary = {}
        for j in range(len(string)-i+1):  #check every combination.

            #count the substring occurrences
            try:
                dictionary[string[j:j+i]] += 1
            except:
                dictionary[string[j:j+i]] = 1

        isUnique = False #loop stops if isUnique is True
        occurrence= 0
        for key in dictionary: #iterate through the dictionary
            if dictionary[key] == 1: #check if any substring is unique
                #if found, get ready to escape from the loop and increase the occurrence
                isUnique = True
                occurrence+=1

        if isUnique: 
            return (i, occurrence)
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值