Go-Python-Java-C-LeetCode高分解法-第七周合集

前言

本题解Go语言部分基于 LeetCode-Go
其他部分基于本人实践学习
个人题解GitHub连接:LeetCode-Go-Python-Java-C

Go-Python-Java-C-LeetCode高分解法-第一周合集
Go-Python-Java-C-LeetCode高分解法-第二周合集
Go-Python-Java-C-LeetCode高分解法-第三周合集
Go-Python-Java-C-LeetCode高分解法-第四周合集
Go-Python-Java-C-LeetCode高分解法-第五周合集
Go-Python-Java-C-LeetCode高分解法-第六周合集
本文部分内容来自网上搜集与个人实践。如果任何信息存在错误,欢迎读者批评指正。本文仅用于学习交流,不用作任何商业用途。
欢迎订阅专栏,每日一题,和博主一起进步
LeetCode专栏

文章目录

  • 前言
  • [43. Multiply Strings](https://leetcode.com/problems/multiply-strings/)
    • 题目
    • 题目大意
    • 解题思路
    • 代码
    • Go
    • Python
    • Java
    • Cpp
  • [44.Wildcard Matching](https://leetcode.com/problems/wildcard-matching/)
    • 题目大意
    • 解题思路
    • 代码
    • Python
    • Java
    • Cpp
  • [45. Jump Game II](https://leetcode.com/problems/jump-game-ii/)
    • 题目
    • 题目大意
    • 解题思路
    • 代码
    • Python
    • Java
    • Cpp
  • [46. Permutations](https://leetcode.com/problems/permutations/)
    • 题目
    • 题目大意
    • 解题思路
    • 代码
    • Go
    • Python
    • Java
    • Cpp
  • [47. Permutations II](https://leetcode.com/problems/permutations-ii/)
    • 题目
    • 题目大意
    • 解题思路
    • 代码
    • Go
    • Python
    • Java
    • Cpp
  • [48. Rotate Image](https://leetcode.com/problems/rotate-image/)
    • 题目
    • 题目大意
    • 解题思路
    • 代码
    • Go
    • Python
    • Java
    • Cpp
  • [49. Group Anagrams](https://leetcode.com/problems/group-anagrams/)
    • 题目
    • 题目大意
    • 解题思路
    • 代码
    • Go
    • Python
    • Java
    • Cpp

43. Multiply Strings

题目

Given two non-negative integersnum1andnum2represented as strings, return the product ofnum1andnum2, also
represented as a string.

**Note:**You must not use any built-in BigInteger library or convert the inputs to integer directly.

Example 1:

Input: num1 = "2", num2 = "3"
Output: "6"

Example 2:

Input: num1 = "123", num2 = "456"
Output: "56088"

Constraints:

  • 1 <= num1.length, num2.length <= 200
  • num1andnum2consist of digits only.
  • Bothnum1andnum2do not contain any leading zero, except the number0itself.

题目大意

给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。

解题思路

  • 用数组模拟乘法。创建一个数组长度为 len(num1) + len(num2)
    的数组用于存储乘积。对于任意 0 ≤ i < len(num1)0 ≤ j < len(num2)num1[i] * num2[j] 的结果位于 tmp[i+j+1]
    ,如果 tmp[i+j+1]≥10,则将进位部分加到 tmp[i+j]。最后,将数组 tmp 转成字符串,如果最高位是 0 则舍弃最高位。
    下面分别介绍每个版本的解题思路:

Go 版本解题思路

  1. 首先,检查输入的 num1num2 是否为 “0”,如果其中一个是 “0”,则返回 “0”,因为任何数乘以 0 都等于 0。

  2. 创建一个整数切片 tmp,其长度为 len(num1) + len(num2),用于存储乘法的中间结果。

  3. 使用嵌套循环遍历 num1num2 的每个字符,将对应位置的数字相乘,并将结果累加到 tmp 中合适的位置。

  4. 处理进位:从右向左遍历 tmp,将每一位的进位加到前一位上,并将当前位取模 10 以保持在 0 到 9 的范围内。

  5. 如果 tmp 的最高位为 0,则去除最高位的 0。

  6. tmp 切片中的数字转换为字符,并构建结果字符串。

  7. 返回结果字符串。

Python 版本解题思路

  1. 首先,检查输入的 num1num2 是否为 “0”,如果其中一个是 “0”,则返回 “0”,因为任何数乘以 0 都等于 0。

  2. 使用内置的 int() 函数将 num1num2 转换为整数,然后将它们相乘,得到整数结果。

  3. 将整数结果转换为字符串,并返回。

Java 版本解题思路

  1. 首先,检查输入的 num1num2 是否为 “0”,如果其中一个是 “0”,则返回 “0”,因为任何数乘以 0 都等于 0。

  2. num1num2 转换为字符数组 b1b2

  3. 创建一个整数数组 tmp,其长度为 num1.length() + num2.length(),用于存储乘法的中间结果。

  4. 使用嵌套循环遍历 b1b2 的每个字符,将对应位置的数字相乘,并将结果累加到 tmp 中合适的位置。

  5. 处理进位:从右向左遍历 tmp,将每一位的进位加到前一位上,并将当前位取模 10 以保持在 0 到 9 的范围内。

  6. 如果 tmp 的最高位为 0,则去除最高位的 0。

  7. 构建结果字符串,将 tmp 数组中的数字转换为字符。

  8. 返回结果字符串。

C++ 版本解题思路

  1. 首先,检查输入的 num1num2 是否为 “0”,如果其中一个是 “0”,则返回 “0”,因为任何数乘以 0 都等于 0。

  2. num1num2 转换为字符数组 b1b2

  3. 创建一个整数数组 tmp,其长度为 num1.length() + num2.length(),用于存储乘法的中间结果。

  4. 使用嵌套循环遍历 b1b2 的每个字符,将对应位置的数字相乘,并将结果累加到 tmp 中合适的位置。

  5. 处理进位:从右向左遍历 tmp,将每一位的进位加到前一位上,并将当前位取模 10 以保持在 0 到 9 的范围内。

  6. 如果 tmp 的最高位为 0,则去除最高位的 0。

  7. 构建结果字符串,将 tmp 数组中的数字转换为字符。

  8. 返回结果字符串。

总体来说,这些版本的解题思路都是类似的,都是模拟手工乘法的过程,将每一位的乘积存储在中间数组中,然后处理进位并构建最终的结果字符串。不同的编程语言具有不同的语法和数据结构,但基本思路是一致的。

代码

Go

func multiply(num1 string, num2 string) string {
    if num1 == "0" || num2 == "0" {
        return "0"
    }

    // 将输入的两个字符串转换为字节数组
    b1, b2, tmp := []byte(num1), []byte(num2), make([]int, len(num1)+len(num2))

    // 使用嵌套循环遍历两个输入字符串的每一位数字进行相乘,结果存储在 tmp 数组中
    for i := 0; i < len(b1); i++ {
        for j := 0; j < len(b2); j++ {
            tmp[i+j+1] += int(b1[i]-'0') * int(b2[j]-'0')
        }
    }

    // 处理进位,将 tmp 数组中的每一位数字都保留在 0 到 9 的范围内
    for i := len(tmp) - 1; i > 0; i-- {
        tmp[i-1] += tmp[i] / 10
        tmp[i] = tmp[i] % 10
    }

    // 如果最高位是0,则去除最高位的0
    if tmp[0] == 0 {
        tmp = tmp[1:]
    }

    // 将结果从整数数组转换回字符串
    res := make([]byte, len(tmp))
    for i := 0; i < len(tmp); i++ {
        res[i] = '0' + byte(tmp[i])
    }
    return string(res)
}

Python

class Solution:
    def multiply(self, num1: str, num2: str) -> str:
        return str(int(num1) * int(num2))

Java

class Solution {
    public String multiply(String num1, String num2) {
        if (num1.equals("0") || num2.equals("0")) {
            return "0";
        }

        char[] b1 = num1.toCharArray();
        char[] b2 = num2.toCharArray();
        int[] tmp = new int[num1.length() + num2.length()];

        for (int i = 0; i < b1.length; i++) {
            for (int j = 0; j < b2.length; j++) {
                tmp[i + j + 1] += (b1[i] - '0') * (b2[j] - '0');
            }
        }

        for (int i = tmp.length - 1; i > 0; i--) {
            tmp[i - 1] += tmp[i] / 10;
            tmp[i] = tmp[i] % 10;
        }

        if (tmp[0] == 0) {
            tmp = Arrays.copyOfRange(tmp, 1, tmp.length);
        }

        StringBuilder result = new StringBuilder();
        for (int num : tmp) {
            result.append(num);
        }

        return result.toString();
    }
}

Cpp

class Solution {
public:
    string multiply(string num1, string num2) {
        if (num1 == "0" || num2 == "0") {
            return "0";
        }

        vector b1(num1.begin(), num1.end());
        vector b2(num2.begin(), num2.end());
        vector tmp(num1.length() + num2.length(), 0);

        for (int i = 0; i < b1.size(); i++) {
            for (int j = 0; j < b2.size(); j++) {
                tmp[i + j + 1] += (b1[i] - '0') * (b2[j] - '0');
            }
        }

        for (int i = tmp.size() - 1; i > 0; i--) {
            tmp[i - 1] += tmp[i] / 10;
            tmp[i] = tmp[i] % 10;
        }

        if (tmp[0] == 0) {
            tmp.erase(tmp.begin());
        }

        string result;
        for (int num : tmp) {
            result += to_string(num);
        }

        return result;
    }
};

当使用不同的编程语言实现同一个算法时,你需要掌握与该语言相关的基础知识和语法。以下是每个版本的基础知识要求的详细介绍:

Go 版本

  1. 数据类型和变量: 了解 Go 中的基本数据类型,如整数、字符串和数组。了解如何声明和使用变量。

  2. 切片 (Slice): Go 中的切片是动态数组,你需要了解如何创建、操作和使用切片来处理字符数组。

  3. 循环: 了解 Go 中的 for 循环语句,以便在两个字符串的字符上执行嵌套循环。

  4. 条件语句: 了解 Go 中的 if 条件语句,以处理特殊情况,如乘数为 “0” 时的情况。

  5. 数组和切片操作: 理解如何访问数组和切片的元素,以及如何进行数组和切片的迭代。

Python 版本

  1. 数据类型和变量: 了解 Python 中的基本数据类型,如整数、字符串和列表。了解如何声明和使用变量。

  2. 字符串操作: Python 中具有强大的字符串处理功能,需要了解如何访问字符串的字符、切片字符串以及将字符串转换为整数。

  3. 循环: 了解 Python 中的 for 循环和 while 循环语句,以便在两个字符串的字符上执行嵌套循环。

  4. 条件语句: 了解 Python 中的 if 条件语句,以处理特殊情况,如乘数为 “0” 时的情况。

  5. 列表 (List): 了解如何创建、操作和使用 Python 中的列表数据结构,以存储中间结果。

Java 版本

  1. 类和对象: Java 是面向对象编程语言,需要了解如何创建类和对象,以组织代码。

  2. 数据类型和变量: 了解 Java 中的基本数据类型,如整数、字符串和字符数组。了解如何声明和使用变量。

  3. 字符数组操作: 了解如何创建字符数组,以便在其中存储字符串的字符,并进行字符之间的操作。

  4. 循环: 了解 Java 中的 for 循环和 while 循环语句,以便在两个字符数组的字符上执行嵌套循环。

  5. 条件语句: 了解 Java 中的 if 条件语句,以处理特殊情况,如乘数为 “0” 时的情况。

  6. 字符串操作: Java 提供了许多字符串处理方法,需要了解如何访问字符串的字符、将字符串转换为整数,以及如何使用 StringBuilder 类构建结果字符串。

C++ 版本

  1. 数据类型和变量: 了解 C++ 中的基本数据类型,如整数、字符串和数组。了解如何声明和使用变量。

  2. 字符数组操作: 了解如何创建字符数组,以便在其中存储字符串的字符,并进行字符之间的操作。

  3. 循环: 了解 C++ 中的 for 循环和 while 循环语句,以便在两个字符数组的字符上执行嵌套循环。

  4. 条件语句: 了解 C++ 中的 if 条件语句,以处理特殊情况,如乘数为 “0” 时的情况。

  5. 字符串操作: C++ 提供了许多字符串处理函数,需要了解如何访问字符串的字符、将字符串转换为整数,以及如何使用 stringstream 构建结果字符串。

无论选择哪个版本,都需要熟悉基本的数据类型、变量声明、循环、条件语句以及与字符串和字符数组相关的操作。此外,了解如何处理特殊情况(例如,一个乘数为 “0”)以及如何将结果从其他数据类型转换为字符串也是解决此问题的关键要点。

44.Wildcard Matching

Given an input string (s) and a pattern ( p), implement wildcard pattern matching with support for ‘?’ and ‘*’ where:

‘?’ Matches any single character.
‘*’ Matches any sequence of characters (including the empty sequence).
The matching should cover the entire input string (not partial).

Example 1:

Input: s = “aa”, p = “a”
Output: false
Explanation: “a” does not match the entire string “aa”.
Example 2:

Input: s = “aa”, p = ""
Output: true
Explanation: '
’ matches any sequence.
Example 3:

Input: s = “cb”, p = “?a”
Output: false
Explanation: ‘?’ matches ‘c’, but the second letter is ‘a’, which does not match ‘b’.

Constraints:

0 <= s.length, p.length <= 2000
s contains only lowercase English letters.
p contains only lowercase English letters, ‘?’ or ‘*’.

题目大意

给你一个输入字符串 (s) 和一个字符模式 § ,请你实现一个支持 ‘?’ 和 ‘’ 匹配规则的通配符匹配:
‘?’ 可以匹配任何单个字符。
'
’ 可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

解题思路

当讨论每个版本的解题思路时,我们将详细介绍每个版本的算法步骤。以下是每个版本的解题思路:

Go版本:

Go版本的解题思路是使用双指针和回溯法来匹配字符串和模式。主要步骤如下:

  1. 首先,使用一个循环来处理模式p末尾的连续*字符。这是为了跳过模式中的多余*

  2. 接着进入一个循环,同时遍历字符串s和模式p

    • 如果当前模式字符是*,记录当前字符串s和模式p的位置,并将模式指针p向后移动一位。
    • 如果当前字符匹配或者是?通配符,将字符串和模式指针同时向后移动一位。
    • 如果字符不匹配且有可回溯的位置,回溯到上一个*的位置,并更新字符串和模式指针。
  3. 循环结束后,检查是否还有未处理的*字符在模式p中。

  4. 最后,检查模式p是否已经完全匹配字符串s。如果模式指针p到达了模式的末尾,表示匹配成功。

Python版本:

Python版本的解题思路与Go版本类似,也是使用双指针和回溯法。主要步骤如下:

  1. 首先,使用一个循环来处理模式p末尾的连续*字符。这是为了跳过模式中的多余*

  2. 接着进入一个循环,同时遍历字符串s和模式p

    • 如果当前模式字符是*,记录当前字符串s和模式p的位置,并将模式指针p向后移动一位。
    • 如果当前字符匹配或者是?通配符,将字符串和模式指针同时向后移动一位。
    • 如果字符不匹配且有可回溯的位置,回溯到上一个*的位置,并更新字符串和模式指针。
  3. 循环结束后,检查是否还有未处理的*字符在模式p中。

  4. 最后,检查模式p是否已经完全匹配字符串s。如果模式指针p到达了模式的末尾,表示匹配成功。

Java版本:

Java版本的解题思路也是使用双指针和回溯法。主要步骤如下:

  1. 首先,使用一个循环来处理模式p末尾的连续*字符。这是为了跳过模式中的多余*

  2. 接着进入一个循环,同时遍历字符串s和模式p

    • 如果当前模式字符是*,记录当前字符串s和模式p的位置,并将模式指针p向后移动一位。
    • 如果当前字符匹配或者是?通配符,将字符串和模式指针同时向后移动一位。
    • 如果字符不匹配且有可回溯的位置,回溯到上一个*的位置,并更新字符串和模式指针。
  3. 循环结束后,检查是否还有未处理的*字符在模式p中。

  4. 最后,检查模式p是否已经完全匹配字符串s。如果模式指针p到达了模式的末尾,表示匹配成功。

C++版本:

C++版本的解题思路与其他版本相似,也是使用双指针和回溯法。主要步骤如下:

  1. 首先,使用一个循环来处理模式p末尾的连续*字符。这是为了跳过模式中的多余*

  2. 接着进入一个循环,同时遍历字符串s和模式p

    • 如果当前模式字符是*,记录当前字符串s和模式p的位置,并将模式指针p向后移动一位。
    • 如果当前字符匹配或者是?通配符,将字符串和模式指针同时向后移动一位。
    • 如果字符不匹配且有可回溯的位置,回溯到上一个*的位置,并更新字符串和模式指针。
  3. 循环结束后,检查是否还有未处理的*字符在模式p中。

  4. 最后,检查模式p是否已经完全匹配字符串s。如果模式指针p到达了模式的末尾,表示匹配成功。

总的来说,无论使用哪种编程语言,解决方案的核心思路都是双指针和回溯法,用于匹配字符串和模式,同时处理通配符*?

代码

func isMatch(s string, p string) bool {
    // 进入循环,只要s和p非空且p的最后一个字符不是'*'
    for len(s) > 0 && len(p) > 0 && p[len(p)-1] != '*' {
        // 如果字符匹配或者p的最后一个字符是'?',则从s和p的末尾去掉一个字符
        if charMatch(s[len(s)-1], p[len(p)-1]) {
            s = s[:len(s)-1]
            p = p[:len(p)-1]
        } else {
            // 如果字符不匹配,返回false
            return false
        }
    }
    // 如果p为空,返回s是否也为空
    if len(p) == 0 {
        return len(s) == 0
    }
    // 初始化索引和记录变量
    sIndex, pIndex := 0, 0
    sRecord, pRecord := -1, -1
    // 开始循环,sIndex小于s的长度且pRecord小于p的长度
    for sIndex < len(s) && pRecord < len(p) {
        // 如果p的当前字符是'*',将p的索引向后移动,记录s和p的位置
        if p[pIndex] == '*' {
            pIndex++
            sRecord, pRecord = sIndex, pIndex
        } else if charMatch(s[sIndex], p[pIndex]) {
            // 如果字符匹配,将s和p的索引都向后移动
            sIndex++
            pIndex++
        } else if sRecord != -1 && sRecord + 1 < len(s) {
            // 如果字符不匹配,但是有记录的位置可用,并且sRecord+1小于s的长度,更新sIndex和pIndex
            sRecord++
            sIndex, pIndex = sRecord, pRecord
        } else {
            // 如果没有符合的情况,返回false
            return false
        }
    }
    // 最后,检查p中是否只包含'*'
    return allStars(p, pIndex, len(p))
}

// 辅助函数,检查字符串中是否都是'*'
func allStars(str string, left, right int) bool {
    for i := left; i < right; i++ {
        if str[i] != '*' {
            return false
        }
    }
    return true
}

// 辅助函数,检查两个字符是否匹配
func charMatch(u, v byte) bool {
    return u == v || v == '?'
}

Python

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        # 进入循环,只要s和p非空且p的最后一个字符不是'*'
        while s and p and p[-1] != '*':
            # 如果字符匹配或者p的最后一个字符是'?',则从s和p的末尾去掉一个字符
            if self.charMatch(s[-1], p[-1]):
                s = s[:-1]
                p = p[:-1]
            else:
                # 如果字符不匹配,返回false
                return False
        # 如果p为空,返回s是否也为空
        if not p:
            return not s
        # 初始化索引和记录变量
        sIndex, pIndex = 0, 0
        sRecord, pRecord = -1, -1
        # 开始循环,sIndex小于s的长度且pRecord小于p的长度
        while sIndex < len(s) and pRecord < len(p):
            # 如果p的当前字符是'*',将p的索引向后移动,记录s和p的位置
            if p[pIndex] == '*':
                pIndex += 1
                sRecord, pRecord = sIndex, pIndex
            elif self.charMatch(s[sIndex], p[pIndex]):
                # 如果字符匹配,将s和p的索引都向后移动
                sIndex += 1
                pIndex += 1
            elif sRecord != -1 and sRecord + 1 < len(s):
                # 如果字符不匹配,但是有记录的位置可用,并且sRecord+1小于s的长度,更新sIndex和pIndex
                sRecord += 1
                sIndex, pIndex = sRecord, pRecord
            else:
                # 如果没有符合的情况,返回false
                return False
        # 最后,检查p中是否只包含'*'
        return self.allStars(p, pIndex, len(p))

    # 辅助函数,检查字符串中是否都是'*'
    def allStars(self, s, left, right):
        for i in range(left, right):
            if s[i] != '*':
                return False
        return True

    # 辅助函数,检查两个字符是否匹配
    def charMatch(self, u, v):
        return u == v or v == '?'

Java

class Solution {
    public boolean isMatch(String s, String p) {
        // 进入循环,只要s和p非空且p的最后一个字符不是'*'
        while (s.length() > 0 && p.length() > 0 && p.charAt(p.length() - 1) != '*') {
            // 如果字符匹配或者p的最后一个字符是'?',则从s和p的末尾去掉一个字符
            if (charMatch(s.charAt(s.length() - 1), p.charAt(p.length() - 1))) {
                s = s.substring(0, s.length() - 1);
                p = p.substring(0, p.length() - 1);
            } else {
                // 如果字符不匹配,返回false
                return false;
            }
        }
        // 如果p为空,返回s是否也为空
        if (p.length() == 0) {
            return s.length() == 0;
        }
        // 初始化索引和记录变量
        int sIndex = 0, pIndex = 0;
        int sRecord = -1, pRecord = -1;
        // 开始循环,sIndex小于s的长度且pRecord小于p的长度
        while (sIndex < s.length() && pRecord < p.length()) {
            // 如果p的当前字符是'*',将p的索引向后移动,记录s和p的位置
            if (p.charAt(pIndex) == '*') {
                pIndex++;
                sRecord = sIndex;
                pRecord = pIndex;
            } else if (charMatch(s.charAt(sIndex), p.charAt(pIndex))) {
                // 如果字符匹配,将s和p的索引都向后移动
                sIndex++;
                pIndex++;
            } else if (sRecord != -1 && sRecord + 1 < s.length()) {
                // 如果字符不匹配,但是有记录的位置可用,并且sRecord+1小于s的长度,更新sIndex和pIndex
                sRecord++;
                sIndex = sRecord;
                pIndex = pRecord;
            } else {
                // 如果没有符合的情况,返回false
                return false;
            }
        }
        // 最后,检查p中是否只包含'*'
        return allStars(p, pIndex, p.length());
    }

    // 辅助函数,检查字符串中是否都是'*'
    private boolean allStars(String str, int left, int right) {
        for (int i = left; i < right; i++) {
            if (str.charAt(i) != '*') {
                return false;
            }
        }
        return true;
    }

    // 辅助函数,检查两个字符是否匹配
    private boolean charMatch(char u, char v) {
        return u == v || v == '?';
    }
}

Cpp

class Solution {
public:
    bool isMatch(string s, string p) {
        int lens = s.size(); // 获取字符串s的长度
        int lenp = p.size(); // 获取模式p的长度
        int scur = 0; // 初始化字符串s的当前指针
        int sstar = -1; // 初始化字符串s的'*'的位置记录
        int pcur = 0; // 初始化模式p的当前指针
        int pstar = -1; // 初始化模式p的'*'的位置记录
        
        while (scur < lens) { // 循环处理字符串s
            if (pcur < lenp && p[pcur] == '*') { // 如果模式p当前字符是'*'
                sstar = scur; // 记录当前字符串s的位置
                pstar = ++pcur; // 记录当前模式p的位置,同时将模式p指针向后移动
            } else {
                if (pcur < lenp && (p[pcur] == '?' || p[pcur] == s[scur])) {
                    // 如果模式p当前字符是'?'或者与字符串s当前字符相匹配
                    scur++; // 移动字符串s的指针
                    pcur++; // 移动模式p的指针
                } else {
                    if (sstar < 0) return false; // 如果没有'*'的位置记录,返回false
                    scur = ++sstar; // 回溯到'*'的位置的下一个字符
                    pcur = pstar; // 恢复模式p的指针到'*'的位置的下一个字符
                }
            }
        }
        
        while (pcur < lenp && p[pcur] == '*') pcur++; // 处理模式p中多余的'*'
        
        return pcur == lenp; // 返回是否模式p已经处理完毕
    }
};

当讨论每个版本的解决方案时,我们将详细介绍所需的基础知识。首先,我们将使用Go、Python、Java和C++的版本进行分析。

Go版本:

  1. 基本语法和数据类型: 在Go中,你需要了解基本的语法和数据类型,包括变量声明、循环、条件语句等。

  2. 字符串操作: 你需要了解如何处理字符串,包括字符串的切片操作和获取字符串的长度。

  3. 循环和条件语句: 代码中使用了循环和条件语句来遍历字符串和执行不同的操作,所以你需要了解这些控制结构。

  4. 切片操作: 在Go中,切片操作是处理字符串和数组的关键操作之一。你需要了解如何截取和操作切片。

Python版本:

  1. 基本语法和数据类型: 在Python中,你需要了解基本的语法和数据类型,包括变量、列表、字符串、循环、条件语句等。

  2. 字符串操作: Python提供了丰富的字符串操作方法,包括切片、拼接、长度等。你需要了解这些操作。

  3. 循环和条件语句: 代码中使用了循环和条件语句来遍历字符串和执行不同的操作,所以你需要了解这些控制结构。

  4. 面向对象编程 (OOP): 尽管在代码中没有使用类和对象,但Python是一种面向对象的编程语言,因此你需要了解OOP的基本概念。

Java版本:

  1. 基本语法和数据类型: 在Java中,你需要了解基本的语法和数据类型,包括变量声明、列表、字符串、循环、条件语句等。

  2. 字符串操作: Java提供了处理字符串的类和方法,你需要了解如何使用String类的方法来操作字符串。

  3. 循环和条件语句: 代码中使用了循环和条件语句来遍历字符串和执行不同的操作,所以你需要了解这些控制结构。

  4. 面向对象编程 (OOP): Java是一种面向对象的编程语言,你需要了解OOP的基本概念,即类、对象、继承等。

C++版本:

  1. 基本语法和数据类型: 在C++中,你需要了解基本的语法和数据类型,包括变量声明、数组、字符串、循环、条件语句等。

  2. 字符串操作: C++提供了处理字符串的标准库,你需要了解如何使用标准库中的字符串函数来操作字符串。

  3. 循环和条件语句: 代码中使用了循环和条件语句来遍历字符串和执行不同的操作,所以你需要了解这些控制结构。

  4. 指针和引用: C++中涉及了指针和引用的概念,尤其是在处理字符串时,你需要了解如何使用指针和引用来操作数据。

总的来说,无论你选择哪种编程语言版本,都需要掌握基本的编程概念、控制结构、字符串操作方法,并根据具体的语言特性来理解和实现算法。每个版本都使用了循环、条件语句、字符串操作等基本编程知识来解决通配符匹配问题。

45. Jump Game II

题目

Given an array of non-negative integers nums, you are initially positioned at the first index of the array.

Each element in the array represents your maximum jump length at that position.

Your goal is to reach the last index in the minimum number of jumps.

You can assume that you can always reach the last index.

Example 1:

Input: nums = [2,3,1,1,4]
Output: 2
Explanation: The minimum number of jumps to reach the last index is 2. Jump 1 step from index 0 to 1, then 3 steps to the last index.

Example 2:

Input: nums = [2,3,0,1,4]
Output: 2

Constraints:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 10^5

题目大意

给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。

解题思路

  • 要求找到最少跳跃次数,顺理成章的会想到用贪心算法解题。扫描步数数组,维护当前能够到达最大下标的位置,记为能到达的最远边界,如果扫描过程中到达了最远边界,更新边界并将跳跃次数 + 1。
  • 扫描数组的时候,其实不需要扫描最后一个元素,因为在跳到最后一个元素之前,能到达的最远边界一定大于等于最后一个元素的位置,不然就跳不到最后一个元素,到达不了终点了;如果遍历到最后一个元素,说明边界正好为最后一个位置,最终跳跃次数直接 + 1 即可,也不需要访问最后一个元素。

Go 版本解题思路

在Go版本中,解决"Jump Game II"问题的思路如下:

  1. 初始化三个变量:needChoose(表示需要选择下一步的位置)、canReach(表示当前可以到达的最远位置)、step(表示跳跃的步数),并将它们都初始化为0。

  2. 遍历输入数组 nums,使用 for i, x := range nums 进行遍历。

  3. 在遍历过程中,检查当前位置 i 加上能够跳跃的最大距离 x 是否大于 canReach,如果是的话,更新 canReachi + x,表示可以到达的更远位置。

  4. 如果 canReach 已经大于等于数组的最后一个位置 len(nums)-1,那么直接返回 step + 1,因为已经能够到达终点了。

  5. 如果当前位置 i 等于 needChoose 所指的位置,说明需要选择下一步的位置了。此时,将 needChoose 更新为 canReach,表示下一步要从 canReach 开始跳跃,同时将 step 加1,表示跳跃了一步。

  6. 最终,返回最小步数 step,这就是达到最后一个位置所需的最小跳跃次数。

Python 版本解题思路

在Python版本中,解决"Jump Game II"问题的思路与Go版本类似:

  1. 初始化三个变量:needChoose(表示需要选择下一步的位置)、canReach(表示当前可以到达的最远位置)、step(表示跳跃的步数),并将它们都初始化为0。

  2. 遍历输入列表 nums,使用 for i in range(len(nums)) 进行遍历。

  3. 在遍历过程中,检查当前位置 i 加上能够跳跃的最大距离 nums[i] 是否大于 canReach,如果是的话,更新 canReachi + nums[i],表示可以到达的更远位置。

  4. 如果 canReach 已经大于等于列表的最后一个位置 len(nums)-1,那么直接返回 step + 1,因为已经能够到达终点了。

  5. 如果当前位置 i 等于 needChoose 所指的位置,说明需要选择下一步的位置了。此时,将 needChoose 更新为 canReach,表示下一步要从 canReach 开始跳跃,同时将 step 加1,表示跳跃了一步。

  6. 最终,返回最小步数 step,这就是达到最后一个位置所需的最小跳跃次数。

Java 版本解题思路

在Java版本中,解决"Jump Game II"问题的思路与Go和Python版本相似:

  1. 初始化三个变量:needChoose(表示需要选择下一步的位置)、canReach(表示当前可以到达的最远位置)、step(表示跳跃的步数),并将它们都初始化为0。

  2. 遍历输入数组 nums,使用 for (int i = 0; i < nums.length; i++) 进行遍历。

  3. 在遍历过程中,检查当前位置 i 加上能够跳跃的最大距离 nums[i] 是否大于 canReach,如果是的话,更新 canReachi + nums[i],表示可以到达的更远位置。

  4. 如果 canReach 已经大于等于数组的最后一个位置 nums.length - 1,那么直接返回 step + 1,因为已经能够到达终点了。

  5. 如果当前位置 i 等于 needChoose 所指的位置,说明需要选择下一步的位置了。此时,将 needChoose 更新为 canReach,表示下一步要从 canReach 开始跳跃,同时将 step 加1,表示跳跃了一步。

  6. 最终,返回最小步数 step,这就是达到最后一个位置所需的最小跳跃次数。

C++ 版本解题思路

在C++版本中,解决"Jump Game II"问题的思路与前述版本相似:

  1. 初始化三个变量:needChoose(表示需要选择下一步的位置)、canReach(表示当前可以到达的最远位置)、step(表示跳跃的步数),并将它们都初始化为0。

  2. 遍历输入向量 nums,使用 for (int i = 0; i < nums.size(); i++) 进行遍历。

  3. 在遍历过程中,检查当前位置 i 加上能够跳跃的最大距离 nums[i] 是否大于 canReach,如果是的话,更新 canReachi + nums[i],表示可以到达的更远位置。

  4. 如果 canReach 已经大于等于向量的最后一个位置 nums.size() - 1,那么直接返回 step + 1,因为已经能够到达终点了。

  5. 如果当前位置 i 等于 needChoose 所指的位置,说明需要选择下一步的位置了。此时,将 needChoose 更新为 canReach,表示下一步要从 canReach 开始跳跃,同时将 step 加1,表示跳跃了一步。

  6. 最终,返回最小步数 step,这就是达到最后一个位置所需的最小跳跃次数。

这就是各个版本的解题思路

代码

func jump(nums []int) int {
    // 如果数组长度为1,无需跳跃,返回0
    if len(nums) == 1 {
        return 0
    }
    // needChoose 表示需要选择下一步的位置,canReach 表示当前可以到达的最远位置,step 表示跳跃的步数
    needChoose, canReach, step := 0, 0, 0
    // 遍历数组
    for i, x := range nums {
        // 如果当前位置加上跳跃力可以到达更远的位置
        if i+x > canReach {
            // 更新 canReach 为更远的位置
            canReach = i + x
            // 如果 canReach 已经可以到达数组末尾,返回步数加1
            if canReach >= len(nums)-1 {
                return step + 1
            }
        }
        // 如果当前位置已经是 needChoose 所指的位置
        if i == needChoose {
            // 更新 needChoose 为 canReach,表示下一步要从 canReach 开始跳跃
            needChoose = canReach
            // 步数加1
            step++
        }
    }
    // 返回最小步数
    return step
}

Python

class Solution:
    def jump(self, nums: List[int]) -> int:
        if len(nums) == 1:
            return 0
        needChoose, canReach, step = 0, 0, 0
        for i in range(len(nums)):
            if i + nums[i] > canReach:
                canReach = i + nums[i]
                if canReach >= len(nums) - 1:
                    return step + 1
            if i == needChoose:
                needChoose = canReach
                step += 1
        return step

Java

class Solution {
    public int jump(int[] nums) {
        if (nums.length == 1) {
            return 0;
        }
        int needChoose = 0, canReach = 0, step = 0;
        for (int i = 0; i < nums.length; i++) {
            if (i + nums[i] > canReach) {
                canReach = i + nums[i];
                if (canReach >= nums.length - 1) {
                    return step + 1;
                }
            }
            if (i == needChoose) {
                needChoose = canReach;
                step++;
            }
        }
        return step;
    }
}

Cpp

class Solution {
public:
    int jump(vector& nums) {
        if (nums.size() == 1) {
            return 0;
        }
        int needChoose = 0, canReach = 0, step = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (i + nums[i] > canReach) {
                canReach = i + nums[i];
                if (canReach >= nums.size() - 1) {
                    return step + 1;
                }
            }
            if (i == needChoose) {
                needChoose = canReach;
                step++;
            }
        }
        return step;
    }
};

每个版本的代码所需要的基础知识。

Go 版本

  • Go(也称为Golang)是一门开源的编程语言,它具有简洁的语法和高效的性能。在这个Go版本的代码中,你需要了解以下基础知识:
    • Go的基本语法,包括变量声明、循环、条件语句等。
    • 切片(slice)和数组(array)的使用,因为题目涉及处理整数数组。
    • 函数的定义和调用,以及函数参数和返回值的处理。
    • 循环结构(for循环)和条件语句(if语句)的使用,因为这些在解决问题时很重要。
    • 切片的操作,如切片扩容、索引访问等,因为这些与跳跃问题相关。

Python 版本

  • Python是一门广泛使用的高级编程语言,以其简单易读的语法而闻名。在这个Python版本的代码中,你需要了解以下基础知识:
    • Python的基本语法,包括变量声明、循环、条件语句等。
    • 列表(List)的使用,因为题目涉及处理整数列表。
    • 类和方法的定义,因为代码使用了类来组织解决方案。
    • 循环结构(for循环)和条件语句(if语句)的使用,因为这些在解决问题时很重要。

Java 版本

  • Java是一门广泛使用的静态类型编程语言,通常用于构建大型应用程序。在这个Java版本的代码中,你需要了解以下基础知识:
    • Java的基本语法,包括变量声明、循环、条件语句等。
    • 数组的使用,因为题目涉及处理整数数组。
    • 类和方法的定义,因为代码使用了类来组织解决方案。
    • 循环结构(for循环)和条件语句(if语句)的使用,因为这些在解决问题时很重要。

C++ 版本

  • C++是一门多范式的编程语言,它结合了高级和低级编程的特性。在这个C++版本的代码中,你需要了解以下基础知识:
    • C++的基本语法,包括变量声明、循环、条件语句等。
    • 向量(Vector)的使用,因为题目涉及处理整数向量。
    • 类和方法的定义,因为代码使用了类来组织解决方案。
    • 循环结构(for循环)和条件语句(if语句)的使用,因为这些在解决问题时很重要。

以上是每个版本代码所需的基础知识概述。如果你需要更详细的解释或有特定问题,欢迎提出。

46. Permutations

题目

Given a collection of distinct integers, return all possible permutations.

Example:

Input: [1,2,3]
Output:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

题目大意

给定一个没有重复数字的序列,返回其所有可能的全排列。

解题思路

  • 求出一个数组的排列组合中的所有排列,用 DFS 深搜即可。

Go 版本解题思路:

  1. 深度优先搜索 (DFS): 这个解法使用深度优先搜索算法来生成所有可能的排列。

  2. DFS 递归函数:dfs 函数中,我们维护了两个重要的参数:pathvisitedpath 存储当前正在生成的排列,visited 用于标记哪些数字已经被使用过。

  3. 递归过程: 在每次递归调用中,我们尝试将未被使用过的数字添加到 path 中,并标记为已使用。然后,递归调用 dfs 函数,继续生成下一个数字。递归的结束条件是 path 的长度等于输入数组 nums 的长度,表示已经生成了一个完整的排列。

  4. 回溯: 当递归返回时,我们需要回溯(backtrack)到上一层状态。这包括将最后一个添加到 path 的数字移除,并将其对应的 visited 标记设为未使用,以便在下一次迭代中尝试其他数字。

  5. 结果收集: 每当我们找到一个完整的排列时,将其复制到一个结果集中(ans),最终返回所有的排列。

Python 版本解题思路:

  1. 深度优先搜索 (DFS): 这个解法同样使用深度优先搜索算法来生成所有可能的排列。

  2. DFS 递归函数:trackback 函数中,我们维护了三个重要参数:curr 存储当前正在生成的排列,used 存储已经使用的数字的索引,nums 是输入数组。

  3. 递归过程: 在每次递归调用中,我们循环遍历未被使用过的数字(通过检查 used 列表),将其添加到 curr 中,并将对应的索引添加到 used 中。然后,递归调用 trackback 函数,继续生成下一个数字。递归的结束条件是 curr 的长度等于输入数组 nums 的长度,表示已经生成了一个完整的排列。

  4. 回溯: 当递归返回时,我们需要回溯(backtrack)到上一层状态。这包括将最后一个添加到 curr 的数字移除,并将其对应的索引从 used 中移除,以便在下一次迭代中尝试其他数字。

  5. 结果收集: 每当我们找到一个完整的排列时,将其添加到结果集中,并最终返回所有的排列。

Java 版本解题思路:

  1. 深度优先搜索 (DFS): 这个解法同样使用深度优先搜索算法来生成所有可能的排列。

  2. DFS 递归函数:dfs 函数中,我们维护了三个重要参数:nums 是输入数组,path 存储当前正在生成的排列,visited 是一个布尔数组,用于标记哪些数字已经被使用过。

  3. 递归过程: 在每次递归调用中,我们循环遍历未被使用过的数字,将其添加到 path 中,并标记为已使用。然后,递归调用 dfs 函数,继续生成下一个数字。递归的结束条件是 path 的长度等于输入数组 nums 的长度,表示已经生成了一个完整的排列。

  4. 回溯: 当递归返回时,我们需要回溯(backtrack)到上一层状态。这包括将最后一个添加到 path 的数字移除,并将其对应的索引的 visited 标记设为未使用,以便在下一次迭代中尝试其他数字。

  5. 结果收集: 每当我们找到一个完整的排列时,将其添加到结果集中(ans),最终返回所有的排列。

C++ 版本解题思路:

  1. 深度优先搜索 (DFS): 这个解法同样使用深度优先搜索算法来生成所有可能的排列。

  2. DFS 递归函数:dfs 函数中,我们维护了四个重要参数:nums 是输入数组,currentPermutation 存储当前正在生成的排列,visited 是一个布尔数组,用于标记哪些数字已经被使用过,ans 是用于存储所有排列的结果。

  3. 递归过程: 在每次递归调用中,我们循环遍历未被使用过的数字,将其添加到 currentPermutation 中,并标记为已使用。然后,递归调用 dfs 函数,继续生成下一个数字。递归的结束条件是 currentPermutation 的长度等于输入数组 nums 的长度,表示已经生成了一个完整的排列。

  4. 回溯: 当递归返回时,我们需要回溯(backtrack)到上一层状态。这包括将最后一个添加到 currentPermutation 的数字移除,并将其对应的索引的 visited 标记设为未使用,以便在下一次迭代中尝试其他数字。

  5. 结果收集: 每当我们找到一个完整的排列时,将其添加到结果集 ans 中,最终返回所有的排列。

代码

Go

func permute(nums []int) [][]int {
    var ans [][]int
    var dfs func(path []int, visited []bool)

    dfs = func(path []int, visited []bool) {
        if len(path) == len(nums) {
            temp := make([]int, len(path))
            copy(temp, path)
            ans = append(ans, temp)
            return
        }

        for i := 0; i < len(nums); i++ {
            if visited[i] {
                continue
            }
            path = append(path, nums[i])
            visited[i] = true
            dfs(path, visited)
            visited[i] = false
            path = path[:len(path)-1]
        }
    }

    visited := make([]bool, len(nums))
    dfs([]int{}, visited)
    return ans
}

Python

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res = []
        self.trackback([], [], nums, res)
        return res
        
    def trackback(self, curr, used, nums, res):
        if len(curr) == len(nums):
            res.append(curr)
            return
        else:
            for i in range(len(nums)):
                if i not in used:
                    self.trackback(curr+[nums[i]], used+[i], nums, res)

Java

class Solution {
    List> ans;  // 用于存储所有全排列的结果
    public List> permute(int[] nums) {
        ans = new ArrayList<>();
        boolean[] visited = new boolean[nums.length];  // 用于标记数字是否已经被访问
        dfs(nums, new ArrayList(), visited);  // 调用深度优先搜索函数
        return ans;  // 返回所有全排列的结果
    }

    public void dfs(int[] nums, List path, boolean[] visited) {
        if (path.size() == nums.length) {  // 如果当前路径的长度等于数组长度,表示找到了一个全排列
            ans.add(new ArrayList<>(path));  // 将当前路径加入结果集
            return;  // 返回上一层继续搜索
        }

        for (int i = 0; i < nums.length; i++) {
            if (visited[i]) {
                continue;  // 如果数字已经被访问过,则跳过
            }
            path.add(nums[i]);  // 将数字加入当前路径
            visited[i] = true;  // 标记数字已经被访问
            dfs(nums, path, visited);  // 递归搜索下一层
            visited[i] = false;  // 恢复标记,以便尝试其他可能的数字
            path.remove(path.size() - 1);  // 移除最后一个数字,回溯到上一层状态
        }
    }
}

Cpp

class Solution {
public:
    vector> permute(vector& nums) {
        vector> ans;
        vector currentPermutation;
        vector visited(nums.size(), false);

        dfs(nums, currentPermutation, visited, ans);

        return ans;
    }

    void dfs(vector& nums, vector& currentPermutation, vector& visited, vector>& ans) {
        if (currentPermutation.size() == nums.size()) {
            ans.push_back(currentPermutation);
            return;
        }

        for (int i = 0; i < nums.size(); i++) {
            if (!visited[i]) {
                currentPermutation.push_back(nums[i]);
                visited[i] = true;
                dfs(nums, currentPermutation, visited, ans);
                visited[i] = false;
                currentPermutation.pop_back();
            }
        }
    }
};

每个版本中需要掌握的基础知识:

Go 版本:

  1. 基础知识

    • Go 语言的基本语法,包括变量、函数、循环、条件语句等。
    • 切片(Slice):Go 中的可变长度数组,用于动态存储数据。
    • 递归:了解递归的概念和使用方法,因为这里使用了递归来生成全排列。
  2. DFS(深度优先搜索)

    • 理解深度优先搜索算法的工作原理,它是解决组合和排列问题的常见方法。
    • 在递归函数中如何维护状态,包括路径和访问标记。

Python 版本:

  1. 基础知识

    • Python 的基本语法,包括列表、循环、条件语句等。
    • 列表(List):Python 中的可变序列,用于存储多个元素。
    • 递归:了解递归的概念和使用方法,因为这里使用了递归来生成全排列。
  2. DFS(深度优先搜索)

    • 理解深度优先搜索算法的工作原理,它是解决组合和排列问题的常见方法。
    • 在递归函数中如何维护状态,包括当前排列、已使用元素列表等。

Java 版本:

  1. 基础知识

    • Java 语言的基本语法,包括类、方法、数组、循环、条件语句等。
    • 列表(List):Java 中的集合类,用于存储多个元素。
    • 递归:了解递归的概念和使用方法,因为这里使用了递归来生成全排列。
  2. DFS(深度优先搜索)

    • 理解深度优先搜索算法的工作原理,它是解决组合和排列问题的常见方法。
    • 在递归函数中如何维护状态,包括当前排列、已使用元素的标记数组等。

C++ 版本:

  1. 基础知识

    • C++ 的基本语法,包括类、函数、数组、循环、条件语句等。
    • 向量(Vector):C++ 中的可变长度数组,用于存储多个元素。
    • 递归:了解递归的概念和使用方法,因为这里使用了递归来生成全排列。
  2. DFS(深度优先搜索)

    • 理解深度优先搜索算法的工作原理,它是解决组合和排列问题的常见方法。
    • 在递归函数中如何维护状态,包括当前排列、已使用元素的标记数组等。

无论选择哪个版本,理解深度优先搜索和递归的概念是关键。这些代码示例都展示了如何使用深度优先搜索来解决排列问题,其中递归是核心的思维方式。同时,了解每种编程语言的语法和数据结构也是很重要的,因为它们在不同语言中可能会有不同的实现方式。

47. Permutations II

题目

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

Example:

Input: [1,1,2]
Output:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

题目大意

给定一个可包含重复数字的序列,返回所有不重复的全排列。

解题思路

  • 这一题是第 46 题的加强版,第 46 题中求数组的排列,数组中元素不重复,但是这一题中,数组元素会重复,所以需要最终排列出来的结果需要去重。
  • 去重的方法是经典逻辑,将数组排序以后,判断重复元素再做逻辑判断。
  • 其他思路和第 46 题完全一致,DFS 深搜即可。

Go 版本解题思路:

  1. 排序数组: 首先,对输入的整数数组 nums 进行排序,这一步很关键,因为它将重复的元素放在一起,为后续去重逻辑做准备。

  2. 深度优先搜索(DFS): 使用深度优先搜索算法,递归地生成排列。从头到尾,逐个考虑数组中的元素是否加入当前排列。

  3. 去重逻辑: 在递归生成排列的过程中,需要判断当前元素是否可以加入排列,以避免重复。如果当前元素与前一个元素相同,且前一个元素未被使用,就跳过当前元素,以防止重复排列。

  4. 结果收集: 每当生成一个完整的排列,就将其添加到结果集中。最终,返回结果集。

Python 版本解题思路:

  1. 排序数组: 同样,首先对输入的整数数组 nums 进行排序,以便处理重复元素。

  2. 深度优先搜索(DFS): 使用深度优先搜索算法递归生成排列。递归的过程中,不断将元素添加到当前排列中。

  3. 去重逻辑: 在递归生成排列的过程中,通过判断元素是否已被使用以及是否与前一个元素相同且前一个元素未被使用,来避免重复。

  4. 结果收集: 每当生成一个完整的排列,就将其添加到结果列表中。最终,返回结果列表。

Java 版本解题思路:

  1. 排序数组: 对输入的整数数组 nums 进行排序,这有助于处理重复元素。

  2. 深度优先搜索(DFS): 使用深度优先搜索算法递归生成排列。递归的过程中,不断将元素添加到当前排列中。

  3. 去重逻辑: 在递归生成排列的过程中,通过判断元素是否已被使用以及是否与前一个元素相同且前一个元素未被使用,来避免重复。

  4. 结果收集: 每当生成一个完整的排列,就将其添加到结果列表中。最终,返回结果列表。

C++ 版本解题思路:

  1. 排序数组: 同样,对输入的整数数组 nums 进行排序,以便处理重复元素。

  2. 深度优先搜索(DFS): 使用深度优先搜索算法递归生成排列。递归的过程中,不断将元素添加到当前排列中。

  3. 去重逻辑: 在递归生成排列的过程中,通过判断元素是否已被使用以及是否与前一个元素相同且前一个元素未被使用,来避免重复。

  4. 结果收集: 每当生成一个完整的排列,就将其添加到结果向量中。最终,返回结果向量。

总的来说,这四个版本的解题思路都基于深度优先搜索(DFS)和去重逻辑,通过不断地添加、移除元素来生成排列并避免重复。排序数组是关键的预处理步骤,确保相同的元素在一起,以便进行去重判断。这些思路是解决排列问题的通用方法,在处理包含重复元素的情况时,需要格外小心去重。

代码

Go

import "sort"

func permuteUnique(nums []int) [][]int {
    if len(nums) == 0 {
        return [][]int{}
    }
    used, p, res := make([]bool, len(nums)), []int{}, [][]int{}
    sort.Ints(nums) // 对输入数组进行排序,关键的去重逻辑
    generatePermutation47(nums, 0, p, &res, &used)
    return res
}

func generatePermutation47(nums []int, index int, p []int, res *[][]int, used *[]bool) {
    if index == len(nums) {
        temp := make([]int, len(p))
        copy(temp, p)
        *res = append(*res, temp) // 将当前排列添加到结果集中
        return
    }
    for i := 0; i < len(nums); i++ {
        if !(*used)[i] {
            if i > 0 && nums[i] == nums[i-1] && !(*used)[i-1] {
                continue // 关键的去重逻辑,跳过重复的数字
            }
            (*used)[i] = true // 标记当前数字已被使用
            p = append(p, nums[i]) // 将当前数字添加到排列中
            generatePermutation47(nums, index+1, p, res, used) // 递归生成下一个位置的排列
            p = p[:len(p)-1] // 回溯,移除当前数字
            (*used)[i] = false // 取消标记当前数字未被使用
        }
    }
    return
}

Python

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        def generatePermutation(nums, used, current, result):
            if len(current) == len(nums):
                result.append(current[:])
                return
            
            for i in range(len(nums)):
                if used[i] or (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]):
                    continue
                
                used[i] = True
                current.append(nums[i])
                generatePermutation(nums, used, current, result)
                current.pop()
                used[i] = False
        
        nums.sort() # 对输入数组进行排序,关键的去重逻辑
        result = []
        used = [False] * len(nums)
        generatePermutation(nums, used, [], result)
        return result

Java

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List> permuteUnique(int[] nums) {
        List> result = new ArrayList<>();
        if (nums == null || nums.length == 0) {
            return result;
        }
        
        Arrays.sort(nums); // 对输入数组进行排序,关键的去重逻辑
        boolean[] used = new boolean[nums.length];
        List current = new ArrayList<>();
        
        generatePermutation(nums, used, current, result);
        
        return result;
    }
    
    private void generatePermutation(int[] nums, boolean[] used, List current, List> result) {
        if (current.size() == nums.length) {
            result.add(new ArrayList<>(current));
            return;
        }
        
        for (int i = 0; i < nums.length; i++) {
            if (used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])) {
                continue;
            }
            
            used[i] = true;
            current.add(nums[i]);
            generatePermutation(nums, used, current, result);
            current.remove(current.size() - 1);
            used[i] = false;
        }
    }
}

Cpp

class Solution {
public:
    vector> permuteUnique(vector& nums) {
        vector> result;
        if (nums.empty()) {
            return result;
        }
        
        sort(nums.begin(), nums.end()); // 对输入数组进行排序,关键的去重逻辑
        vector used(nums.size(), false);
        vector current;
        
        generatePermutation(nums, used, current, result);
        
        return result;
    }
    
private:
    void generatePermutation(vector& nums, vector& used, vector& current, vector>& result) {
        if (current.size() == nums.size()) {
            result.push_back(current);
            return;
        }
        
        for (int i = 0; i < nums.size(); i++) {
            if (used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])) {
                continue;
            }
            
            used[i] = true;
            current.push_back(nums[i]);
            generatePermutation(nums, used, current, result);
            current.pop_back();
            used[i] = false;
        }
    }
};

相关的基础知识要点。

Go 版本:

  • 基础知识:
    • Go 语言基础:熟悉 Go 语法、数据结构、切片(slice)、循环、条件语句等基本概念。
    • 切片和数组:了解 Go 中的切片(slice)和数组的区别以及如何使用它们。
    • 递归:理解递归的概念和如何在 Go 中实现递归函数。

Python 版本:

  • 基础知识:
    • Python 基础:掌握 Python 的语法、数据类型、列表(list)、循环、条件语句等基本知识。
    • 列表和集合:了解 Python 中的列表和集合数据结构,以及它们的操作和特性。
    • 递归:理解递归的概念和如何在 Python 中实现递归函数。

Java 版本:

  • 基础知识:
    • Java 基础:熟悉 Java 的语法、数组、列表(List)、循环、条件语句等基本知识。
    • 列表和集合:了解 Java 中的列表(List)和集合(Set)数据结构,以及它们的使用方法。
    • 递归:理解递归的概念和如何在 Java 中实现递归方法。

C++ 版本:

  • 基础知识:
    • C++ 基础:掌握 C++ 的语法、数组、向量(vector)、循环、条件语句等基本概念。
    • 向量(vector):了解 C++ 中的向量(vector)数据结构,以及如何使用它来存储和处理数据。
    • 递归:理解递归的概念和如何在 C++ 中实现递归函数。

48. Rotate Image

题目

You are given an n x n 2D matrix representing an image.

Rotate the image by 90 degrees (clockwise).

Note:

You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

Example 1:

Go-Python-Java-C-LeetCode高分解法-第七周合集_第1张图片

Given input matrix = 
[
  [1,2,3],
  [4,5,6],
  [7,8,9]
],

rotate the input matrix in-place such that it becomes:
[
  [7,4,1],
  [8,5,2],
  [9,6,3]
]

Example 2:

Go-Python-Java-C-LeetCode高分解法-第七周合集_第2张图片

Given input matrix =
[
  [ 5, 1, 9,11],
  [ 2, 4, 8,10],
  [13, 3, 6, 7],
  [15,14,12,16]
], 

rotate the input matrix in-place such that it becomes:
[
  [15,13, 2, 5],
  [14, 3, 4, 1],
  [12, 6, 8, 9],
  [16, 7,10,11]
]

题目大意

给定一个 n × n 的二维矩阵表示一个图像。将图像顺时针旋转 90 度。说明:你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

解题思路

  • 给出一个二维数组,要求顺时针旋转 90 度。
  • 这一题比较简单,按照题意做就可以。这里给出 2 种旋转方法的实现,顺时针旋转和逆时针旋转。
    当解决问题 “Rotate Image” 时,每个版本的解题思路基本相同,只是具体语言的语法和操作略有不同。以下是每个版本的解题思路:

Go 版本解题思路

  1. 对角线变换:首先,我们进行对角线变换,将矩阵中的元素按照主对角线(从左上角到右下角)进行交换。这一步实际上是将矩阵顺时针旋转了 90 度的一半。

  2. 竖直轴对称翻转:接下来,我们对每一行进行竖直轴对称翻转。这一步将完成矩阵的剩余旋转,使其顺时针旋转 90 度。

Python 版本解题思路

Python 版本的解题思路与 Go 版本基本相同,包括对角线变换和竖直轴对称翻转两个步骤。

Java 版本解题思路

Java 版本的解题思路也与 Go 版本类似:

  1. 对角线变换:首先,我们进行对角线变换,将矩阵中的元素按照主对角线进行交换。

  2. 竖直轴对称翻转:接下来,对每一行进行竖直轴对称翻转,完成矩阵的剩余旋转。

C++ 版本解题思路

C++ 版本的解题思路与 Java 版本非常相似:

  1. 对角线变换:首先,我们进行对角线变换,将矩阵中的元素按照主对角线进行交换。

  2. 竖直轴对称翻转:然后,对每一行进行竖直轴对称翻转,完成矩阵的剩余旋转。

总之,每个版本的解题思路都遵循了对角线变换和竖直轴对称翻转这两个步骤,以实现矩阵的顺时针旋转 90 度。如果您有任何特定版本的问题或需要更详细的解释,请告诉我。

代码

Go

// 解法一
func rotate(matrix [][]int) {
    length := len(matrix) // 获取矩阵的长度

    // rotate by diagonal 对角线变换
    for i := 0; i < length; i++ { // 遍历行
        for j := i + 1; j < length; j++ { // 遍历列,从当前行的下一个元素开始
            matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] // 交换矩阵[i][j]和矩阵[j][i]的值,实现对角线翻转
        }
    }

    // rotate by vertical centerline 竖直轴对称翻转
    for i := 0; i < length; i++ { // 遍历每一行
        for j := 0; j < length/2; j++ { // 遍历每一行的前一半列
            matrix[i][j], matrix[i][length-j-1] = matrix[i][length-j-1], matrix[i][j] // 交换矩阵[i][j]和矩阵[i][length-j-1]的值,实现竖直轴对称翻转
        }
    }
}

Python

class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        length = len(matrix)

        # rotate by diagonal 对角线变换
        for i in range(length):
            for j in range(i + 1, length):
                matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]

        # rotate by vertical centerline 竖直轴对称翻转
        for i in range(length):
            for j in range(length // 2):
                matrix[i][j], matrix[i][length - j - 1] = matrix[i][length - j - 1], matrix[i][j]

Java

class Solution {
    public void rotate(int[][] matrix) {
        int length = matrix.length;

        // rotate by diagonal 对角线变换
        for (int i = 0; i < length; i++) {
            for (int j = i + 1; j < length; j++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }

        // rotate by vertical centerline 竖直轴对称翻转
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < length / 2; j++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[i][length - j - 1];
                matrix[i][length - j - 1] = temp;
            }
        }
    }
}

Cpp

class Solution:
    def rotate(self, matrix: List[List[int]]) -> None:
        length = len(matrix)

        # rotate by diagonal 对角线变换
        for i in range(length):
            for j in range(i + 1, length):
                matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]

        # rotate by vertical centerline 竖直轴对称翻转
        for i in range(length):
            for j in range(length // 2):
                matrix[i][j], matrix[i][length - j - 1] = matrix[i][length - j - 1], matrix[i][j]

Go 版本

  1. Slice(切片):在 Go 中,切片是一种灵活的数据结构,用于处理动态数组。在这个问题中,我们使用切片来表示二维矩阵。

  2. 循环和迭代:Go 使用 for 循环来遍历数组和切片。在这里,我们使用嵌套的 for 循环来遍历矩阵的行和列。

  3. 交换变量值:在 Go 中,可以通过多重赋值来交换两个变量的值。这是实现矩阵元素交换的关键。

Python 版本

  1. 列表:在 Python 中,我们使用列表来表示数组或矩阵。这里的矩阵就是一个二维列表。

  2. 循环和迭代:Python 使用 for 循环来遍历列表。在这个问题中,我们使用嵌套的 for 循环来遍历矩阵的行和列。

  3. 多重赋值:Python 允许使用多重赋值来交换两个变量的值,这对于交换矩阵元素很有用。

Java 版本

  1. 二维数组:Java 使用二维数组来表示矩阵。在这个问题中,我们将矩阵表示为 int[][] 类型。

  2. 嵌套循环:Java 使用嵌套 for 循环来遍历二维数组。第一个循环用于遍历行,第二个循环用于遍历列。

  3. 临时变量:我们使用一个临时变量来在交换两个矩阵元素的值时进行存储。

C++ 版本

  1. 二维数组:C++ 也使用二维数组来表示矩阵。矩阵的类型是 vector>

  2. 循环和迭代:C++ 使用 for 循环来遍历向量(vector)。嵌套的 for 循环用于遍历二维向量。

  3. 临时变量:和 Java 一样,我们使用一个临时变量来交换两个矩阵元素的值。

这些是解决这个问题所需的基础知识,包括数据结构和编程概念,如循环、迭代和变量操作。如果您有任何关于这些版本的特定问题或需要更详细的解释,请随时提出。

49. Group Anagrams

题目

Given an array of strings, group anagrams together.

Example:

Input: ["eat", "tea", "tan", "ate", "nat", "bat"],
Output:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]

Note:

  • All inputs will be in lowercase.
  • The order of your output does not matter.

题目大意

给出一个字符串数组,要求对字符串数组里面有 Anagrams 关系的字符串进行分组。Anagrams 关系是指两个字符串的字符完全相同,顺序不同,两者是由排列组合组成。

解题思路

这道题可以将每个字符串都排序,排序完成以后,相同 Anagrams 的字符串必然排序结果一样。把排序以后的字符串当做 key 存入到 map
中。遍历数组以后,就能得到一个 map,key 是排序以后的字符串,value 对应的是这个排序字符串以后的 Anagrams 字符串集合。最后再将这些
value 对应的字符串数组输出即可。
当然,以下是每个版本的解题思路:

Go 版本解题思路

  1. 创建一个空的映射 (map),用于将具有相同字母组成的字符串分组。
  2. 创建一个空的结果切片 (slice)。
  3. 定义一个匿名函数 sign,该函数接受一个字符串作为输入,并返回一个表示字符串字母组成的标识符。
  4. 遍历输入的字符串数组:
    • 对每个字符串计算其字母组成的标识符,通过匿名函数 sign 实现。
    • 将当前字符串添加到对应标识符的分组中,使用映射进行组织。
  5. 遍历映射中的每个分组,将分组转换为切片,并将切片添加到结果切片中。
  6. 返回最终的结果切片,其中包含了按字母组成分组的字符串。

Python 版本解题思路

  1. 创建一个空的字典 (dictionary),用于将具有相同字母组成的字符串分组。
  2. 遍历输入的字符串列表:
    • 将每个字符串排序,形成一个元组,该元组将被用作字典的键。
    • 如果该键不存在于字典中,则创建一个空列表作为值,并将原始字符串添加到该列表中。
    • 如果该键已经存在于字典中,则将原始字符串添加到对应键的列表中。
  3. 将字典的值(分组)转换为列表,并返回结果。

Java 版本解题思路

  1. 创建一个映射 (Map),用于将具有相同字母组成的字符串分组。在 Java 中,通常使用 HashMap
  2. 遍历输入的字符串数组:
    • 将每个字符串转换为字符数组,并对字符数组进行排序,形成排序后的字符串。
    • 使用排序后的字符串作为映射的键,原始字符串作为值存入映射中。
  3. 将映射中的分组转换为列表,并返回结果。

C++ 版本解题思路

  1. 创建一个映射 (unordered_map),用于将具有相同字母组成的字符串分组。
  2. 遍历输入的字符串向量:
    • 将每个字符串排序,形成排序后的字符串。
    • 使用排序后的字符串作为映射的键,原始字符串作为值存入映射中。
  3. 将映射的值(分组)转换为向量,并返回结果。

无论使用哪种版本的解决方案,基本思路都是利用数据结构(如映射或字典)来组织和存储具有相同字母组成的字符串,然后将它们分组起来。排序字符串是为了确保相同
Anagrams 的字符串具有相同的标识符,以便正确地分组它们。

代码

Go

func groupAnagrams(strs []string) [][]string {
    // 创建一个空的映射(map),用于将具有相同字母组成的字符串分组
    hashMap := map[string][]string{}
    // 创建一个空的结果切片(slice)
    res := [][]string{}

    // 定义一个匿名函数 sign,该函数接受一个字符串 s,并返回一个表示 s 字母组成的标识符
    sign := func(s string) string {
        // 创建一个长度为 26 的字节数组,用于统计每个字母出现的次数
        strB := [26]byte{}
        // 遍历字符串 s 中的每个字符
        for _, v := range s {
            // 将字符 v 转换为小写字母,并将对应字母的计数加一
            strB[v-'a']++
        }
        // 将字节数组转换为字符串并返回
        return string(strB[:])
    }

    // 遍历输入的字符串切片 strs
    for _, v := range strs {
        // 对当前字符串 v 计算其字母组成的标识符
        signV := sign(v)
        // 将当前字符串添加到对应标识符的分组中
        hashMap[signV] = append(hashMap[signV], v)
    }

    // 遍历映射中的每个分组,并将其添加到结果切片 res 中
    for _, v := range hashMap {
        res = append(res, v)
    }
    // 返回最终的结果切片,其中包含了按字母组成分组的字符串
    return res
}

Python

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        # 创建一个字典,用于将具有相同字母组成的字符串分组
        hashMap = {}

        # 遍历输入的字符串列表
        for str in strs:
            # 将字符串转换为排序后的元组,作为字典的键
            sorted_str = tuple(sorted(str))
            # 将原始字符串添加到对应键的列表中
            hashMap.setdefault(sorted_str, []).append(str)

        # 将字典的值(分组)转换为列表,并返回结果
        result = list(hashMap.values())
        return result

Java

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Solution {
    public List> groupAnagrams(String[] strs) {
        // 创建一个映射,用于将具有相同字母组成的字符串分组
        Map> hashMap = new HashMap<>();

        // 遍历输入的字符串数组
        for (String str : strs) {
            // 将字符串转换为字符数组,并排序
            char[] charArray = str.toCharArray();
            Arrays.sort(charArray);
            // 排序后的字符数组作为键,原始字符串作为值存入映射中
            String sortedStr = new String(charArray);
            if (!hashMap.containsKey(sortedStr)) {
                hashMap.put(sortedStr, new ArrayList<>());
            }
            hashMap.get(sortedStr).add(str);
        }

        // 将映射中的分组转换为列表
        List> result = new ArrayList<>(hashMap.values());
        return result;
    }
}

Cpp

class Solution {
public:
    vector> groupAnagrams(vector& strs) {
        // 创建一个映射,用于将具有相同字母组成的字符串分组
        unordered_map> hashMap;
        
        // 遍历输入的字符串向量
        for (string str : strs) {
            // 将字符串排序,作为映射的键
            string sortedStr = str;
            sort(sortedStr.begin(), sortedStr.end());
            // 将原始字符串添加到对应键的向量中
            hashMap[sortedStr].push_back(str);
        }
        
        // 将映射的值(分组)转换为向量,并返回结果
        vector> result;
        for (auto& pair : hashMap) {
            result.push_back(pair.second);
        }
        return result;
    }
};

每个版本的所需基础知识:

Go 版本

  • Go 语言基础: 了解 Go 语言的基本语法,包括变量声明、函数定义、条件语句、循环等。
  • 切片 (Slices) 和映射 (Maps): 了解如何使用切片和映射来处理集合数据。在这个解决方案中,使用了切片和映射来组织和存储分组的
    Anagrams。
  • 匿名函数 (Anonymous Functions): 了解如何定义匿名函数,如解决方案中的 sign 函数。
  • 字符操作: 了解如何处理字符串,包括字符的遍历和比较。

Python 版本

  • Python 基础: 熟悉 Python 的基本语法,包括变量、列表、循环、条件语句等。
  • 字典 (Dictionaries) 和列表 (Lists): 了解如何使用字典和列表来处理集合数据。在这个解决方案中,使用字典来组织和存储分组的
    Anagrams。
  • 字符串操作: 熟悉 Python 字符串的基本操作,包括字符串排序。
  • 元组 (Tuples): 了解如何使用元组,如解决方案中的排序后的字符串元组。

Java 版本

  • Java 基础: 理解 Java 的基本语法,包括类和方法的定义、循环、条件语句等。
  • 集合框架 (Collections Framework): 了解 Java 集合框架,包括 HashMapArrayList
    的使用。在这个解决方案中,使用 HashMap 来组织和存储分组的 Anagrams。
  • 字符操作和排序: 熟悉 Java 中的字符操作,包括将字符串转换为字符数组并进行排序。

C++ 版本

  • C++ 基础: 了解 C++ 的基本语法,包括变量声明、函数定义、循环、条件语句等。
  • STL (Standard Template Library): 熟悉 C++ STL,包括 unordered_mapvector
    的使用。在这个解决方案中,使用 unordered_map 来组织和存储分组的 Anagrams。
  • 字符操作和排序: 了解如何在 C++ 中处理字符串,包括将字符串排序。

无论你选择哪种语言的版本,都需要基本的编程知识,包括掌握基本的语法和数据结构操作,以便理解和修改这些解决方案。此外,了解算法的基本思想对理解这些代码也会有帮助。

你可能感兴趣的:(LeetCode,golang,python,java)