编程练习题(6)

买卖股票的最佳时机

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

如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

注意:你不能在买入股票前卖出股票。

Java解法:

/*
    一次遍历
    用一个变量记录一个历史最低价格 minprice,就可以假设股票是在那天买的。那么在第 i 天卖出股票能得到的利润就是 prices[i] - minprice。
    因此,只需要遍历价格数组一遍,记录历史最低点,然后比较 prices[i] - minprice > max

     */

    public static int maxProfit(int[] prices) {
    
    
        int min = Integer.MAX_VALUE;
        int max = 0;
        for (int i = 0 ; i < prices.length ; i++){
    
    
            if (min > prices[i]){
    
    
                min = prices[i];
            }else if (prices[i] - min > max){
    
    
                max = prices[i] - min;
            }
        }
        return max;
    }

Python解法:

# 买卖股票的最佳时机
class Solution:
    def maxProfit(self, prices: list) -> int:
        minp = int(1e9)
        maxp = 0
        for i in range(len(prices)):
            if minp > prices[i]:
                minp = prices[i]
            elif prices[i] - minp > maxp:
                maxp = prices[i] - minp
        return maxp


# 测试
s = Solution()
prices = [7,1,5,3,6,4]
print(s.maxProfit(prices))

# 答案:
5

买卖股票的最佳时机 II

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

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

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

Java解法:

public static int maxProfit(int[] prices) {
    
    
        int res = 0;
        for (int i = 1 ; i < prices.length ; i++){
    
    
            if (prices[i-1] < prices[i]){
    
    
                res += prices[i] - prices[i-1];
            }
        }
        return res;
    }

Python解法:

# 买卖股票的最佳时机 II
class Solution:
    def maxProfit(self, prices: list) -> int:
        res = 0
        for i in range(1, len(prices)):
            if prices[i] > prices[i-1]:
                res += prices[i] - prices[i-1]

        return res

# 测试
s = Solution()
prices = [7,1,5,3,6,4]
print(s.maxProfit(prices))

# 答案:
7

买卖股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

Java解法:

/*
    一天一共就有五个状态,
        0. 没有操作
        1. 第一次买入
        2. 第一次卖出
        3. 第二次买入
        4. 第二次卖出
        dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。
        确定递推公式:dp[i][0] = dp[i - 1][0];
        需要注意:dp[i][1],表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票
        达到dp[i][1]状态,有两个具体操作:
            操作一:第i天买入股票了,那么dp[i][1] = dp[i-1][0] - prices[i]
            操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]
            所以 dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);
        同理dp[i][2]也有两个操作:
            操作一:第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
            操作二:第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]
            所以dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])
        同理可推出剩下状态部分:
            dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
            dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
        初始化:
            第0天没有操作,,即:dp[0][0] = 0;
            第0天做第一次买入的操作,dp[0][1] = -prices[0];
            第0天做第一次卖出的操作,这个初始值应该是多少呢?
            首先卖出的操作一定是收获利润,整个股票买卖最差情况也就是没有盈利即全程无操作现金为0,
            从递推公式中可以看出每次是取最大值,那么既然是收获利润如果比0还小了就没有必要收获这个利润了。
            所以dp[0][2] = 0;
            第0天第二次买入操作,初始值应该是多少呢?
            所以第二次买入操作,初始化为:dp[0][3] = -prices[0];
            同理第二次卖出初始化dp[0][4] = 0;

     */

    public static int maxProfit(int[] prices) {
    
    
        int n = prices.length;
        if (prices.length == 0){
    
    
            return 0;
        }
        int []dp = new int[5];
        dp[0] = dp[2] = dp[4] = 0;
        dp[1] = dp[3] = -prices[0];
        for (int i = 1 ; i < n ; i++){
    
    
            dp[1] = Math.max(dp[1], dp[0] - prices[i]);
            dp[2] = Math.max(dp[2], dp[1] + prices[i]);
            dp[3] = Math.max(dp[3], dp[2] - prices[i]);
            dp[4] = Math.max(dp[4], dp[3] + prices[i]);
        }
        return dp[4];
    }

Python解法:

class Solution:
    def maxProfit(self, prices: list) -> int:
        if not prices or len(prices) == 0:
            return 0
        n = len(prices)
        dp = [0 for _ in range(5)]
        dp[0] = dp[2] = 0
        dp[1] = dp[3] = -prices[0]
        for i in range(1, n):
            dp[1] = max(dp[1], dp[0] - prices[i])
            dp[2] = max(dp[2], dp[1] + prices[i])
            dp[3] = max(dp[3], dp[2] - prices[i])
            dp[4] = max(dp[4], dp[3] + prices[i])
        return dp[4]

# 测试
s = Solution()
prices = [3,3,5,0,0,3,1,4]
print(s.maxProfit(prices))

# 答案:
6

二叉树中的最大路径和

路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和 。

Java解法:

/*
    递归
     */
    int maxSum = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
    
    
        backTrack(root);
        return maxSum;
    }

    public int backTrack(TreeNode root) {
    
    
        if (root == null) {
    
    
            return 0;
        }

        /* 递归计算左右子节点的最大值, 只有在最大贡献值大于 0 时,才会选取对应子节点 */
        int leftGain = Math.max(backTrack(root.left), 0);
        int rightGain = Math.max(backTrack(root.right), 0);

        /* 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值 */
        int curpath = root.val + leftGain + rightGain;

        // 更新答案
        maxSum = Math.max(maxSum, curpath);

        // 返回节点的最大贡献值
        return root.val + Math.max(leftGain, rightGain);
    }

求根到叶子节点数字之和

给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。

例如,从根到叶子节点路径 1->2->3 代表数字 123。

计算从根到叶子节点生成的所有数字之和。

Java解法:

public static int sumNumbers(TreeNode root) {
    
    
        return dfs(root, 0);
    }

    public static int dfs(TreeNode root , int presum){
    
    
        if (root == null){
    
    
            return 0;
        }
        int sum = presum * 10 + root.val;
        if (root.left == null && root.right == null){
    
    
            return sum;
        }else{
    
    
            return dfs(root.left, sum) + dfs(root.right, sum);
        }
    }

验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

Java解法:

/*
    在原字符串 ss 上使用双指针。
    在移动任意一个指针时,需要不断地向另一指针的方向移动,直到遇到一个字母或数字字符,或者两指针重合为止,然后进行判断
     */

    public boolean isPalindrome(String s) {
    
    
        int left = 0, right = s.length()-1;
        while (left < right){
    
    
            while (left < right && !Character.isLetterOrDigit(s.charAt(left))){
    
    
                left++;
            }
            while (left < right && !Character.isLetterOrDigit(s.charAt(right))){
    
    
                right--;
            }
            if (left < right){
    
    
                if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))){
    
    
                    return false;
                }
                left++;
                right--;
            }
        }
        return true;
    }

Python解法:

# 验证回文串
class Solution:
    def isPalindrome(self, s: str) -> bool:
        left, right = 0, len(s)-1
        while left < right:
            # isalnum,检测字符串是否由字母和数字组成
            while left < right and not s[left].isalnum():
                left += 1
            while left < right and not s[right].isalnum():
                right -= 1
            if left < right:
                if s[left].lower() != s[right].lower():
                    return False
                left += 1
                right -= 1
        return True

# 测试
d = Solution()
s = "A man, a plan, a canal: Panama"
print(d.isPalindrome(s))

# 答案:
True

最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

Java解法:

/*
    思路:
        准备一个HashSet,将所有元素入set,之后遍历数组中的每一个数num
        如果 num - 1 存在于set中,那么num不可能是左边界,直接跳过
        如果 num - 1 不存在于set中,那么num会是一个左边界,
        我们再不断地查找num+1、num+2......是否存在于set中,来看以num为左边界的连续序列能有多长

     */

    public static int longestConsecutive(int[] nums) {
    
    
        HashSet<Integer> set = new HashSet<>();
        for (int num : nums){
    
    
            set.add(num);
        }
        int res = 0;
        for (int num : nums){
    
    
            if (set.contains(num-1)){
    
    
                continue;
            }else{
    
    
                int len = 0;
                while (set.contains(num++)){
    
    
                    len++;
                }
                res = Math.max(len, res);
            }
        }
        return res;
    }

Python解法:

# 最长连续序列
class Solution:
    def longestConsecutive(self, nums: list) -> int:
        num_set = set(nums)
        res = 0
        for num in num_set:
            if (num-1) in num_set:
                continue
            else:
                len = 0
                while num in num_set:
                    len += 1
                    num += 1
                res = max(res, len)

        return res

# 测试
s = Solution()
nums = [100,4,200,1,3,2]
print(s.longestConsecutive(nums))

# 测试
4

被围绕的区域

给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。

找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。

Java解法:

/*
    注意到题目解释中提到:任何边界上的 O 都不会被填充为 X。 我们可以想到,所有的不被包围的 O 都直接或间接与边界上的 O 相连。我们可以利用这个性质判断 O 是否在边界上,具体地说:
        对于每一个边界上的 O,我们以它为起点,标记所有与它直接或间接相连的字母 O;
        最后我们遍历这个矩阵,对于每一个字母:
        如果该字母被标记过,则该字母为没有被字母 X 包围的字母 O,我们将其还原为字母 O;
        如果该字母没有被标记过,则该字母为被字母 X 包围的字母 O,我们将其修改为字母 X。
        把标记过的字母 O 修改为字母 #
     */

    public static void solve(char[][] board) {
    
    
        if (board == null || board.length == 0) {
    
    
            return;
        }
        int n = board.length;
        int m = board[0].length;
        for (int i = 0; i < n; i++) {
    
    
            dfs(board, i, 0, m, n);
            dfs(board, i, m - 1, m, n);
        }
        for (int i = 1; i < m - 1; i++) {
    
    
            dfs(board, 0, i, m, n);
            dfs(board, n - 1, i, m, n);
        }

        for (int i = 0; i < n; i++) {
    
    
            for (int j = 0; j < m; j++) {
    
    
                if (board[i][j] == '#') {
    
    
                    board[i][j] = 'O';
                } else if (board[i][j] == 'O') {
    
    
                    board[i][j] = 'X';
                }
            }
        }
    }

    public static void dfs(char[][] board, int x, int y, int m, int n) {
    
    
        if (x < 0 || x >= n || y < 0 || y >= m || board[x][y] != 'O') {
    
    
            return;
        }
        board[x][y] = '#';
        dfs(board, x + 1, y, m, n);
        dfs(board, x - 1, y, m, n);
        dfs(board, x, y + 1, m, n);
        dfs(board, x, y - 1, m, n);
    }

Python解法:

#被围绕的区域
class Solution:
    def solve(self, board: list) -> None:
        if not board:
            return

        n, m = len(board), len(board[0])

        def dfs(x, y):
            if not 0 <= x < n or not 0 <= y < m or board[x][y] != 'O':
                return

            board[x][y] = "#"
            dfs(x + 1, y)
            dfs(x - 1, y)
            dfs(x, y + 1)
            dfs(x, y - 1)

        for i in range(n):
            dfs(i, 0)
            dfs(i, m - 1)

        for i in range(m - 1):
            dfs(0, i)
            dfs(n - 1, i)

        for i in range(n):
            for j in range(m):
                if board[i][j] == "#":
                    board[i][j] = "O"
                elif board[i][j] == "O":
                    board[i][j] = "X"


# 测试
s = Solution()
board = [
            ['X','X','X','X'],
            ['X','O','O','X'],
            ['X','X','O','X'],
            ['X','O','X','X']
        ]
s.solve(board)
print(board)

# 答案:
[['X', 'X', 'X', 'X'], ['X', 'X', 'X', 'X'], ['X', 'X', 'X', 'X'], ['X', 'O', 'X', 'X']]

分隔回文串

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

Java解法:

 /*
    递归回溯
     */
    /*
    public static List<List<String>> partition(String s) {
        List<List<String>> res = new ArrayList<>();
        backtrack(res, s,  new ArrayList<String>());
        return res;
    }

    private static void backtrack(List<List<String>> res, String s, ArrayList<String> tmp) {
        if (s == null || s.length() == 0){
            res.add(new ArrayList<>(tmp));
        }
        for (int i = 1 ; i <= s.length() ; i++){
            if (isValid(s.substring(0, i))){
                tmp.add(s.substring(0, i));
                backtrack(res, s.substring(i, s.length()), tmp);
                tmp.remove(tmp.size() - 1);
            }
        }
    }

    public static boolean isValid(String s){
        int left = 0, right = s.length()-1;
        while (left < right){
            if (s.charAt(left++) != s.charAt(right--)){
                return false;
            }
        }
        return true;
    }
    */
    public static List<List<String>> partition(String s) {
    
    
        List<List<String>> res = new ArrayList<>();
        int n = s.length();
        if (n == 0){
    
    
            return res;
        }
        // 预处理,dp[i][j] 表示 s[i][j] 是否回文
        boolean [][]dp = new boolean[n][n];
        //转态转移方程:在s[i] == s[j] 的时候,dp[i][j] 参考dp[i+1][j-1]
        for (int right = 0 ; right < n ; right++){
    
    
            for (int left = 0 ; left <= right ; left++){
    
    
                if (s.charAt(left) == s.charAt(right) && ((right - left) <= 2 || dp[left+1][right-1])){
    
    
                    dp[left][right] = true;
                }
            }
        }
        Deque<String> stack = new ArrayDeque<>();
        backtracking(s, 0, n, dp, stack, res);
        return res;
    }

    private static void backtracking(String s, int start, int len, boolean[][] dp, Deque<String> path, List<List<String>> res) {
    
    
        if (start == len) {
    
    
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = start; i < len; i++) {
    
    
            // 剪枝
            if (!dp[start][i]) {
    
    
                continue;
            }
            path.addLast(s.substring(start, i + 1));
            backtracking(s, i + 1, len, dp, path, res);
            path.removeLast();
        }
    }

Python解法:

# 分割回文串
"""
class Solution:
    def partition(self, s: str) -> list:
        res = []
        length = len(s)
        if length == 0:
            return res
        path = []
        self.dfs(s, length, 0, path, res)
        return res

    def dfs(self, s, length, start, path, res):
        if start == length:
            res.append(path[:])
            return
        for i in range(start, length):
            if not self.isValid(s, start, i):
                continue
            path.append(s[start:i + 1])
            self.dfs(s, length, i + 1, path, res)
            path.pop()

    def isValid(self, s, left, right) -> bool:
        while left < right:
            if s[left] != s[right]:
                return False
            left = left + 1
            right = right - 1
        return True
"""
class Solution:
    def partition(self, s: str) -> list:
        if not s:
            return []
        if len(s) == 1:
            return [[s]]

        m = len(s)
        res = []
        # dp数组在这里记载的是,[i,j]双闭区间能否构成回文串。(1)若能构成,则dp[i,j]则为回文串长度。(2)若不能,dp[i,j]则为0
        dp = [[0 for _ in range(m)] for _ in range(m)]
        for j in range(0, m):
            for i in range(0, j+1):
                if s[i] == s[j]:
                    if j-i < 3 or dp[i+1][j-1] > 0:
                        dp[i][j] = j-i+1
                    else:
                        dp[i][j] = 0
                else:
                    dp[i][j] = 0
        temp_result = []
        self.dfs(0, temp_result, dp, res, s)
        return res

    # 深度优先遍历,对于当前的位置i,看一下[i,j]能否构成回文串,若能,则继续深度优先遍历,到达极限则记录结果。记得回溯
    def dfs(self, i, temp, dp, res, s):
        if i == len(dp):
            res.append(temp.copy())

        for j in range(i, len(dp)):
            if dp[i][j]:
                temp.append(s[i:j+1])
                self.dfs(j+1, temp, dp, res, s)
                temp.pop()




# 测试
s = Solution()
print(s.partition('aab'))

# 答案:
[['a', 'a', 'b'], ['aa', 'b']]

分割回文串 II

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回符合要求的最少分割次数

Java解法:

/*
    动态规划:
        dp[i]:表示前缀子串 s[0:i] 分割成若干个回文子串所需要最小分割次数
        如果 s[0:i] 本身就是一个回文串,那么不用分割,即 dp[i] = 0 ,这是首先可以判断的,否则就需要去遍历;
        接下来枚举可能分割的位置:即如果 s[0:i] 本身不是一个回文串,就尝试分割,枚举分割的边界 j。
        如果 s[j + 1, i] 不是回文串,尝试下一个分割边界。
        如果 s[j + 1, i] 是回文串,则 dp[i] 就是在 dp[j] 的基础上多一个分割。
        于是枚举 j 所有可能的位置,取所有 dp[j] 中最小的再加 1 ,就是 dp[i]。
        得到状态转移方程如下:
            dp[i] = min([dp[j] + 1 for j in range(i) if s[j + 1, i] 是回文])
        初始状态:单个字符一定是回文串,因此 dp[0] = 0
     */

    public static int minCut(String s) {
    
    
        int n = s.length();
        if (n < 2){
    
    
            return 0;
        }
        int []dp = new int[n];
        for (int i = 0 ; i < n ; i++){
    
    
            dp[i] = i;
        }
        for (int i = 1; i < n ; i++){
    
    
            if (isValid(s, 0, i)){
    
    
                dp[i] = 0;
                continue;
            }
            //当 j == i 成立的时候,s[i]就是一个字符,一定是回文,因此,枚举到 i - 1 即可
            for (int j = 0 ; j < i ; j++){
    
    
                if (isValid(s,j+1, i)){
    
    
                    dp[i] = Math.min(dp[i], dp[j]+1);
                }
            }
        }
        return dp[n-1];
    }

    public static boolean isValid(String s , int left, int right){
    
    
        while (left < right){
    
    
            if (s.charAt(left++) != s.charAt(right--)){
    
    
                return false;
            }
        }
        return true;
    }

Python解法:

# Python方法超时了
# "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

class Solution:
    def minCut(self, s: str) -> int:
        if len(s) < 2:
            return 0
        n = len(s)
        dp = [0 for _ in range(n)]
        for i in range(n):
            dp[i] = i
        for i in range(1, n):
            if self.isValid(s, 0, i):
                dp[i] = 0
                continue
            # 当 j == i成立的时候,s[i]就是一个字符,一定是回文,因此,枚举到 i - 1 即可
            for j in range(0, i):
                if self.isValid(s, j+1, i):
                    dp[i] = min(dp[i], dp[j]+1)
        return dp[n-1]

    def isValid(self, s, left, right):
        while left < right:
            if s[left] != s[right]:
                return False
            left += 1
            right -= 1
        return True

# 测试
s = Solution()
print(s.minCut('aab'))

# 答案:
1

克隆图

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 val(int) 和其邻居的列表

Java解法:

 /*
    深度优先搜索
    1.使用一个哈希表存储所有已被访问和克隆的节点。哈希表中的 key 是原始图中的节点,value 是克隆图中的对应节点。
    2.从给定节点开始遍历图。如果某个节点已经被访问过,则返回其克隆图中的对应节点。
    3.如果当前访问的节点不在哈希表中,则创建它的克隆节点并存储在哈希表中。
        注意:在进入递归之前,必须先创建克隆节点并保存在哈希表中。如果不保证这种顺序,可能会在递归中再次遇到同一个节点,再次遍历该节点时,陷入死循环。
    4.递归调用每个节点的邻接点。每个节点递归调用的次数等于邻接点的数量,每一次调用返回其对应邻接点的克隆节点,
        最终返回这些克隆邻接点的列表,将其放入对应克隆节点的邻接表中。这样就可以克隆给定的节点和其邻接点。
     */

    static public HashMap<Node, Node> visited = new HashMap<>();

    public Node cloneGraph(Node node) {
    
    
        if (node == null){
    
    
            return node;
        }
        /*如果该节点已经被访问过,直接从哈希表取出对应克隆节点返回*/
        if (visited.containsKey(node)){
    
    
            return visited.get(node);
        }
        /*克隆节点,注意为了深拷贝,不能去克隆它的邻居的列表*/
        Node cloneNode = new Node(node.val, new ArrayList<>());
        /*哈希表存储*/
        visited.put(node, cloneNode);
        /*遍历节点的邻居并更新克隆节点的邻居列表*/
        for (Node neighbor : node.neighbors){
    
    
            cloneNode.neighbors.add(cloneGraph(neighbor));
        }
        return cloneNode;
    }

加油站

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

说明:

如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。

Java解法:

   /*
    从头到尾遍历每个加油站,并检查以该加油站为起点,最终能否行驶一周
     */
    public static int canCompleteCircuit(int[] gas, int[] cost) {
    
    
        int n = gas.length;
        int i = 0;

        while (i < n){
    
    
            int sumGas = 0; //总共加的油
            int sumCost = 0;    //总共消费的油
            int count = 0;  //能走过几个站点
            while (count < n){
    
    
                int j = (i + count) % n;    //加油站是环形的
                sumGas += gas[j];
                sumCost += cost[j];
                if (sumCost > sumGas){
    
      //油不够
                    break;
                }
                count++;    //该站点满足情况
            }
            if (count == n){
    
        //满足情况
                return i;
            }else{
    
      //不行就从下一个站点开始检查
                i = i + count + 1;
            }
        }
        return -1;
    }

Python解法:

# 加油站
class Solution:
    def canCompleteCircuit(self, gas: list, cost: list) -> int:
        n = len(gas)
        i = 0
        while i < n:
            # 总共加的油,消费的油,能走过几个站点
            sumGas, sumCost, count = 0, 0, 0
            while count < n:
                j = (i + count) % n
                sumGas += gas[j]
                sumCost += cost[j]
                if sumCost > sumGas:
                    break
                count += 1
            if count == n:
                return i
            else:
                i = i + count + 1
        return -1

# 测试
s = Solution()
gas  = [1,2,3,4,5]
cost = [3,4,5,1,2]
print(s.canCompleteCircuit(gas, cost))

# 答案:
3

分发糖果

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

你需要按照以下要求,帮助老师给这些孩子分发糖果:

每个孩子至少分配到 1 个糖果。
评分更高的孩子必须比他两侧的邻位孩子获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?

Java解法:

 /*
    左规则:当 ratings[i - 1] < ratings[i] 时,i 号学生的糖果数量将比 i - 1 号孩子的糖果数量多。
    右规则:当 ratings[i] > ratings[i + 1] 时,i 号学生的糖果数量将比 i + 1 号孩子的糖果数量多。
     */

    public static int candy(int[] ratings) {
    
    
        int n = ratings.length;
        int []left = new int[n];
        for (int i = 0 ; i < n ; i++){
    
    
            if (i > 0 && ratings[i] > ratings[i-1]){
    
    
                left[i] = left[i-1]+1;
            }else{
    
    
                left[i] = 1;
            }
        }
        int right = 0, res = 0;
        for (int i = n - 1 ; i >= 0 ; i--){
    
    
            if (i < n-1 && ratings[i] > ratings[i+1]){
    
    
                right++;
            }else{
    
    
                right = 1;
            }
            res += Math.max(left[i], right);
        }
        return res;
    }

Python解法:

# 分发糖果
# 左规则:当 ratings[i - 1] < ratings[i] 时,i 号学生的糖果数量将比 i - 1 号孩子的糖果数量多。
# 右规则:当 ratings[i] > ratings[i + 1] 时,i 号学生的糖果数量将比 i + 1 号孩子的糖果数量多。
class Solution:
    def candy(self, ratings: list) -> int:
        n = len(ratings)
        left = [0 for _ in range(n)]
        for i in range(0, n):
            if i > 0 and ratings[i] > ratings[i-1]:
                left[i] = left[i-1] + 1
            else:
                left[i] = 1
        right, res = 0, 0
        for i in range(n-1, -1, -1):
            if i < n-1 and ratings[i] > ratings[i+1]:
                right += 1
            else:
                right = 1
            res += max(right, left[i])

        return res

# 测试
s = Solution()
ratings = [1,0,2]
print(s.candy(ratings))

# 测试
5

只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

Java解法:

/*
    用 hashset 来做
     */

    public static int singleNumber(int[] nums) {
    
    
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0 ; i < nums.length ; i++){
    
    
            if (!set.contains(nums[i])){
    
    
                set.add(nums[i]);
            }else{
    
    
                set.remove(nums[i]);
            }
        }
        return (int)set.toArray()[0];
    }

Python解法:

# 只出现一次的数字
class Solution:
    def singleNumber(self, nums: list) -> int:
        num_set = set()
        for i in range(len(nums)):
            if nums[i] not in num_set:
                num_set.add(nums[i])
            else:
                num_set.remove(nums[i])
        return num_set.pop()

# 测试
nums = [2,2,1]
s = Solution()
print(s.singleNumber(nums))

# 答案:
1

只出现一次的数字 II

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。

Java解法:

 /*
    用HashMap键值对特性来解决
    */

    public int singleNumber(int[] nums) {
    
    
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int val : nums){
    
    
            map.put(val, map.getOrDefault(val,0) + 1);
        }
        for (int key : map.keySet()){
    
    
            if (map.get(key) == 1){
    
    
                return key;
            }
        }
        return -1;
    }

Python解法:

# 只出现一次的数字 II
# 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
from collections import Counter
class Solution:
    def singleNumber(self, nums: list) -> int:
        map = Counter(nums)
        for k in map.keys():
            if map[k] == 1:
                return k

# 测试
s = Solution()
nums = [2,2,3,2]
print(s.singleNumber(nums))

# 测试
3

复制带随机指针的链表

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。

要求返回这个链表的 深拷贝。

我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。

/*
第一遍复制节点的 val ,next 和 random 暂时为空,并将源节点和克隆节点形成映射存放在一个 HashMap 中。
第二遍赋值next,random 
*/
public Node copyRandomList(Node head) {
    
    
    if (head == null) return null;
    HashMap<Node, Node> map = new HashMap<>();
    Node p = head;
    while (p != null) {
    
    
        map.put(p, new Node(p.val));
        p = p.next;
    }
    p = head;
    while (p != null) {
    
    
        map.get(p).next = map.get(p.next);
        map.get(p).random = map.get(p.random);
        p = p.next;
    }
    return map.get(head);
}

单词接龙

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。

说明:
如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

Java解法:

public static int ladderLength(String beginWord, String endWord, List<String> wordList) {
    
    
        // 第 1 步:先将 wordList 放到哈希表里,便于判断某个单词是否在 wordList 里
        Set<String> wordSet = new HashSet<>(wordList);
        if (wordSet.size() == 0 || !wordSet.contains(endWord)) {
    
    
            return 0;
        }

        // 第 2 步:已经访问过的 word 添加到 visited 哈希表里
        Set<String> visited = new HashSet<>();
        // 分别用左边和右边扩散的哈希表代替单向 BFS 里的队列,它们在双向 BFS 的过程中交替使用
        Set<String> beginVisited = new HashSet<>();
        beginVisited.add(beginWord);
        Set<String> endVisited = new HashSet<>();
        endVisited.add(endWord);

        // 第 3 步:执行双向 BFS,左右交替扩散的步数之和为所求
        int step = 1;
        while (!beginVisited.isEmpty() && !endVisited.isEmpty()) {
    
    
            // 优先选择小的哈希表进行扩散,考虑到的情况更少
            if (beginVisited.size() > endVisited.size()) {
    
    
                Set<String> temp = beginVisited;
                beginVisited = endVisited;
                endVisited = temp;
            }

            // 逻辑到这里,保证 beginVisited 是相对较小的集合,nextLevelVisited 在扩散完成以后,会成为新的 beginVisited
            Set<String> nextLevelVisited = new HashSet<>();
            for (String word : beginVisited) {
    
    
                if (changeWordEveryOneLetter(word, endVisited, visited, wordSet, nextLevelVisited)) {
    
    
                    return step + 1;
                }
            }

            // 原来的 beginVisited 废弃,从 nextLevelVisited 开始新的双向 BFS
            beginVisited = nextLevelVisited;
            step++;
        }
        return 0;
    }


    /**
     * 尝试对 word 修改每一个字符,看看是不是能落在 endVisited 中,扩展得到的新的 word 添加到 nextLevelVisited 里
     *
     * @param word
     * @param endVisited
     * @param visited
     * @param wordSet
     * @param nextLevelVisited
     * @return
     */
    private static boolean changeWordEveryOneLetter(String word, Set<String> endVisited,
                                             Set<String> visited,
                                             Set<String> wordSet,
                                             Set<String> nextLevelVisited) {
    
    
        char[] charArray = word.toCharArray();
        for (int i = 0; i < word.length(); i++) {
    
    
            char originChar = charArray[i];
            for (char c = 'a'; c <= 'z'; c++) {
    
    
                if (originChar == c) {
    
    
                    continue;
                }
                charArray[i] = c;
                String nextWord = String.valueOf(charArray);
                if (wordSet.contains(nextWord)) {
    
    
                    if (endVisited.contains(nextWord)) {
    
    
                        return true;
                    }
                    if (!visited.contains(nextWord)) {
    
    
                        nextLevelVisited.add(nextWord);
                        visited.add(nextWord);
                    }
                }
            }
            // 恢复,下次再用
            charArray[i] = originChar;
        }
        return false;
    }

单词拆分

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

Java解法:

/*
    动态规划:定义 dp[i] 表示字符串 s 前 i 个字符组成的字符串 s[0..i−1] 是否能被空格拆分成若干个字典中出现的单词
        dp[i]=dp[j] && check(s[j..i−1])
        其中 check(s[j..i−1]) 表示子串 s[j..i−1] 是否出现在字典中。
     */

    public static boolean wordBreak(String s, List<String> wordDict) {
    
    
        int n = s.length();
        boolean []dp = new boolean[n+1];
        dp[0] = true;
        for (int i = 1; i <= n; i++){
    
    
            for (int j = 0; j < i ; j++ ){
    
    
                if (dp[j] && wordDict.contains(s.substring(j, i))){
    
    
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[n];
    }

Python解法:

# 单词拆分
class Solution:
    def wordBreak(self, s: str, wordDict: list) -> bool:
        n = len(s)
        dp = [False for _ in range(n+1)]
        dp[0] = True
        for i in range(1, n+1):
            for j in range(0, i):
                if dp[j] and s[j:i] in wordDict:
                    dp[i] = True
                    break

        return dp[n]

# 测试
d = Solution()
s = "leetcode"
wordDict = ["leet", "code"]
print(d.wordBreak(s, wordDict))

# 答案:
True

更新中

猜你喜欢

转载自blog.csdn.net/qq_42748009/article/details/112672455