本题解Go语言部分基于 LeetCode-Go
其他部分基于本人实践学习
个人题解GitHub连接:LeetCode-Go-Python-Java-C
本文部分内容来自网上搜集与个人实践。如果任何信息存在错误,欢迎读者批评指正。本文仅用于学习交流,不用作任何商业用途。
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
在数组中找到 2 个数之和等于给定值的数字,结果返回 2 个数字在数组中的下标。
这道题最优的做法时间复杂度是 O(n)。
顺序扫描数组,对每一个元素,在 map 中找能组合给定值的另一半数字,如果找到了,直接返回 2 个数字的下标即可。如果找不到,就把这个数字存入 map 中,等待扫到“另一半”数字的时候,再取出来返回结果。
Python:
Java:
C++:
func twoSum(nums []int, target int) []int {
// 定义map用于存储元素和索引
m := make(map[int]int)
// 遍历数组
for i := 0; i < len(nums); i++ {
// 计算另一个需要的数
another := target - nums[i]
// 在map中查找another是否存在
if _, ok := m[another]; ok {
// 如果存在,返回两个数的索引
return []int{m[another], i}
}
// 不存在,将当前元素和索引存入map
m[nums[i]] = i
}
// 遍历完成未找到,返回nil
return nil
}
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 定义字典保存元素和索引映射
num_map = {}
# 遍历nums列表,enumerate可以同时获得迭代对象的索引和值
for i, num in enumerate(nums):
# 计算需要的另一个数
another = target - num
# 判断another是否在num_map中
if another in num_map:
# 如果在,返回两个数的索引
return [num_map[another], i]
# 如果不在,将当前num和索引添加到num_map
num_map[num] = i
# 遍历结束没有找到,返回空列表
return []
class Solution {
public int[] twoSum(int[] nums, int target) {
// 定义HashMap保存数值和索引
Map numMap = new HashMap<>();
// 遍历数组
for (int i = 0; i < nums.length; i++) {
// 计算需要的另一个数
int another = target - nums[i];
// 判断map中是否存在该数
if (numMap.containsKey(another)) {
// 如果存在直接返回两个数的索引
return new int[] {numMap.get(another), i};
}
// 不存在则将当前数和索引放入map
numMap.put(nums[i], i);
}
// 遍历结束没有结果则返回空数组
return new int[0];
}
}
#include
#include
class Solution {
public:
std::vector twoSum(std::vector& nums, int target) {
std::unordered_map m; // 创建一个unordered_map用于存储元素和索引的对应关系
std::vector result;
for (int i = 0; i < nums.size(); i++) {
int another = target - nums[i]; // 计算需要的另一个数
if (m.find(another) != m.end()) { // 在map中查找是否存在该数
result.push_back(m[another]); // 将对应的索引添加到结果数组中
result.push_back(i);
return result;
}
m[nums[i]] = i; // 将当前元素和索引存入map
}
return result; // 遍历完成后仍未找到符合条件的数对,返回空的结果数组
}
};
四个版本两数之和解法所需的基础知识:
Go 版本:
Python 版本:
Java 版本:
C++版本:
You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order
and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
Example:
Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8
Explanation: 342 + 465 = 807.
2 个逆序的链表,要求从低位开始相加,得出结果也逆序输出,返回值是逆序结果链表的头结点。
需要注意的是各种进位问题。
极端情况,例如
Input: (9 -> 9 -> 9 -> 9 -> 9) + (1 -> )
Output: 0 -> 0 -> 0 -> 0 -> 0 -> 1
为了处理方法统一,可以先建立一个虚拟头结点,这个虚拟头结点的 Next 指向真正的 head,这样 head 不需要单独处理,直接 while
循环即可。另外判断循环终止的条件不用是 p.Next != nil,这样最后一位还需要额外计算,循环终止条件应该是 p != nil。
对于Go版本的解题思路:
对于Python版本的解题思路:
对于Java版本的解题思路:
对于C++版本的解题思路:
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
// 创建一个空节点作为返回链表的头节点
head := &ListNode{Val: 0}
// 定义变量 n1、n2 表示链表 l1 和 l2 的当前节点值
// carry 表示当前位的进位
// current 指向返回链表的当前节点
n1, n2, carry, current := 0, 0, 0, head
// 当 l1、l2 任意一个链表未遍历完 或 有进位时,继续循环
for l1 != nil || l2 != nil || carry != 0 {
// 如果 l1 链表已遍历完,将 n1 设为 0
if l1 == nil {
n1 = 0
} else {
// 否则取 l1 当前节点值
n1 = l1.Val
// l1 指针前移
l1 = l1.Next
}
// 对 l2 链表进行同样处理
if l2 == nil {
n2 = 0
} else {
n2 = l2.Val
l2 = l2.Next
}
// 求当前位相加结果
// 新建节点,节点值是当前位求和对 10 取余的结果
current.Next = &ListNode{Val: (n1 + n2 + carry) % 10}
// current 指针前移
current = current.Next
// 计算下一位的进位
carry = (n1 + n2 + carry) / 10
}
// 返回头节点的下一个节点,作为链表结果
return head.Next
}
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
head = ListNode() # 创建一个空节点作为返回链表的头节点
current = head # current指向返回链表的当前节点
carry = 0 # carry表示当前位的进位
while l1 or l2 or carry:
n1 = l1.val if l1 else 0
n2 = l2.val if l2 else 0
sum = n1 + n2 + carry
carry = sum // 10 # 计算下一位的进位
current.next = ListNode(sum % 10) # 新节点,节点值是当前位求和对10取余的结果
current = current.next # current指针前移
if l1:
l1 = l1.next # l1指针前移
if l2:
l2 = l2.next # l2指针前移
return head.next # 返回头节点的下一个节点,作为链表结果
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = new ListNode(); // 创建一个空节点作为返回链表的头节点
ListNode current = head; // current指向返回链表的当前节点
int carry = 0; // carry表示当前位的进位
while (l1 != null || l2 != null || carry != 0) {
int n1 = (l1 != null) ? l1.val : 0;
int n2 = (l2 != null) ? l2.val : 0;
int sum = n1 + n2 + carry;
carry = sum / 10; // 计算下一位的进位
current.next = new ListNode(sum % 10); // 新建节点,节点值是当前位求和对10取余的结果
current = current.next; // current指针前移
if (l1 != null) l1 = l1.next; // l1指针前移
if (l2 != null) l2 = l2.next; // l2指针前移
}
return head.next; // 返回头节点的下一个节点,作为链表结果
}
}
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
#include
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* head = new ListNode(); // 创建一个空节点作为返回链表的头节点
ListNode* current = head; // current指向返回链表的当前节点
int carry = 0; // carry表示当前位的进位
while (l1 != nullptr || l2 != nullptr || carry != 0) {
int n1 = (l1 != nullptr) ? l1->val : 0;
int n2 = (l2 != nullptr) ? l2->val : 0;
int sum = n1 + n2 + carry;
carry = sum / 10; // 计算下一位的进位
current->next = new ListNode(sum % 10); // 新建节点,节点值是当前位求和对10取余的结果
current = current->next; // current指针前移
if (l1 != nullptr) l1 = l1->next; // l1指针前移
if (l2 != nullptr) l2 = l2->next; // l2指针前移
}
return head->next; // 返回头节点的下一个节点,作为链表结果
}
};
对于Go版本,你需要掌握以下基础知识:
对于Python版本,你需要掌握以下基础知识:
对于Java版本,你需要掌握以下基础知识:
对于C++版本,你需要掌握以下基础知识:
Given a string, find the length of the longest substring without repeating characters.
Example 1:
Input: "abcabcbb"
Output: 3
Explanation: The answer is "abc", with the length of 3.
Example 2:
Input: "bbbbb"
Output: 1
Explanation: The answer is "b", with the length of 1.
Example 3:
Input: "pwwkew"
Output: 3
Explanation: The answer is "wke", with the length of 3.
Note that the answer must be a substring, "pwke" is a subsequence and not a substring.
在一个字符串中寻找没有重复字母的最长子串。
这一题和第 438 题,第 3 题,第 76 题,第 567 题类似,用的思想都是"滑动窗口"。
滑动窗口的右边界不断的右移,只要没有重复的字符,就持续向右扩大窗口边界。一旦出现了重复字符,就需要缩小左边界,直到重复的字符移出了左边界,然后继续移动滑动窗口的右边界。以此类推,每次移动需要计算当前长度,并判断是否需要更新最大长度,最终最大的值就是题目中的所求。
Go
解法一:滑动窗口 + Map
解法二:滑动窗口 + 数组
解法三:滑动窗口 + Bitmap
Python
解法一:滑动窗口 + 集合
解法二:滑动窗口 + 字符频次数组
解法三:滑动窗口 + 哈希表
解法四:位图
Java
解法一:滑动窗口 + 位图
C++
解法一:滑动窗口 + 位图
解法二:滑动窗口 + 数组
解法三:滑动窗口 + 哈希表
// 解法一 位图
func lengthOfLongestSubstring(s string) int {
if len(s) == 0 { // 如果传入的字符串长度为0,直接返回0
return 0
}
var bitSet [256]bool // 定义一个256位的布尔数组,用来标记每个字符是否出现过
result, left, right := 0, 0, 0 // 定义结果、左指针、右指针,初始化为0
for left < len(s) {
// 如果当前右指针指向的字符在位图中被标记为true,说明该字符重复出现过
// 需要左指针向右移动,直到将重复的字符的对应位标记为false
if bitSet[s[right]] {
bitSet[s[left]] = false
left++
} else { // 如果没有重复,将该字符对应位标记为true
bitSet[s[right]] = true
right++
}
// 计算当前窗口大小,更新结果
if result < right-left {
result = right - left
}
// 如果左指针到达字符串结尾,或者右指针超过了字符串长度,结束循环
if left+result >= len(s) || right >= len(s) {
break
}
}
return result // 返回结果
}
// 解法二 滑动窗口
func lengthOfLongestSubstring(s string) int {
if len(s) == 0 { // 字符串为空,返回0
return 0
}
var freq [127]int // 定义一个数组,记录ASCII字符出现的频率
result, left, right := 0, 0, -1
for left < len(s) {
// 如果右指针还可以移动,且对应的字符频率为0,右指针右移
if right+1 < len(s) && freq[s[right+1]] == 0 {
freq[s[right+1]]++
right++
} else { // 否则左指针右移,对应字符频率减1
freq[s[left]]--
left++
}
// 计算当前窗口大小,更新结果
result = max(result, right-left+1)
}
return result
}
func max(a int, b int) int { // 比较两个数大小,返回较大值
if a > b {
return a
}
return b
}
// 解法三 滑动窗口-哈希桶
func lengthOfLongestSubstring(s string) int {
right, left, res := 0, 0, 0 // 初始化右指针、左指针和结果
indexes := make(map[byte]int, len(s)) // map存储每个字符最后出现的index
for left < len(s) {
if idx, ok := indexes[s[left]]; ok && idx >= right { // 如果该字符已存在且在窗口中
right = idx + 1 // 右指针移动到重复字符处
}
indexes[s[left]] = left // 更新字符索引
left++ // 左指针右移
res = max(res, left-right) // 计算窗口大小,更新结果
}
return res
}
func max(a int, b int) int {
if a > b {
return a
}
return b
}
# 解法一:滑动窗口 + 集合
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if len(s) == 0:
return 0 # 字符串为空,直接返回0
char_set = set() # 定义一个集合,记录每个字符是否出现过
l = 0
res = 0
for r in range(len(s)):
while s[r] in char_set: # 如果右指针指向的字符已在集合中
char_set.remove(s[l]) # 从集合中删除左指针位置字符
l += 1 # 左指针右移
char_set.add(s[r]) # 将右指针指向的字符添加到集合
res = max(res, r-l+1) # 计算当前窗口大小,更新结果
return res
# 解法二:滑动窗口 + 字符频次数组
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if len(s) == 0:
return 0 # 字符串为空,直接返回0
freq = [0] * 128 # 定义数组,记录每个ASCII字符出现的频率
l = 0
res = 0
for r in range(len(s)):
while freq[ord(s[r])] > 0: # 如果右指针字符频率大于0,表示重复
freq[ord(s[l])] -= 1 # 左指针位置字符频率减1
l += 1 # 左指针右移
freq[ord(s[r])] += 1 # 右指针字符频率加1
res = max(res, r-l+1) # 计算窗口大小,更新结果
return res
# 解法三:滑动窗口 + 哈希表
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
l = 0
res = 0
index = {} # 字典存储字符最后出现的index
for r in range(len(s)):
if s[r] in index and index[s[r]] >= l: # 字符已存在且在窗口中
l = index[s[r]] + 1 # 左指针移动到重复字符处
index[s[r]] = r # 更新字符index
res = max(res, r-l+1) # 计算窗口大小,更新结果
return res
# 解法四:位图
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
bitmap = [False] * 256 # 初始化一个256位的位图
l = 0
res = 0
for r in range(len(s)):
while bitmap[ord(s[r])]: # 右指针字符已被标记
bitmap[ord(s[l])] = False # 重置左指针字符
l += 1
bitmap[ord(s[r])] = True # 标记右指针字符
res = max(res, r - l + 1) # 更新结果
return res
class Solution {
// 解法一:位图
public int lengthOfLongestSubstring(String s) {
if (s.length() == 0) {
return 0;
}
// 使用一个位图数组来标记字符是否出现过
boolean[] bitSet = new boolean[256];
int result = 0;
int left = 0, right = 0;
while (left < s.length()) {
// 如果右侧字符对应的bitSet被标记为true,说明此字符在X位置重复,需要左侧���前移动,直到将X标记为false
if (bitSet[s.charAt(right)]) {
bitSet[s.charAt(left)] = false;
left++;
} else {
bitSet[s.charAt(right)] = true;
right++;
}
if (result < right - left) {
result = right - left;
}
// 如果左侧加上结果大于等于字符串长度,或者右侧超过字符串长度,跳出循环
if (left + result >= s.length() || right >= s.length()) {
break;
}
}
return result;
}
}
class Solution {
// 解法二:滑动窗口
public int lengthOfLongestSubstring(String s) {
if (s.length() == 0) {
return 0;
}
int[] freq = new int[127];
int result = 0, left = 0, right = -1;
while (left < s.length()) {
if (right + 1 < s.length() && freq[s.charAt(right + 1)] == 0) {
freq[s.charAt(right + 1)]++;
right++;
} else {
freq[s.charAt(left)]--;
left++;
}
result = Math.max(result, right - left + 1);
}
return result;
}
}
class Solution {
// 解法三:滑动窗口-哈希桶
public int lengthOfLongestSubstring(String s) {
int right = 0, left = 0, res = 0;
Map indexes = new HashMap<>();
while (left < s.length()) {
if (indexes.containsKey(s.charAt(left)) && indexes.get(s.charAt(left)) >= right) {
right = indexes.get(s.charAt(left)) + 1;
}
indexes.put(s.charAt(left), left);
left++;
res = Math.max(res, left - right);
}
return res;
}
}
#include
#include
class Solution {
public:
// 解法一:位图
int lengthOfLongestSubstring(string s) {
if (s.length() == 0) {
return 0;
}
// 使用一个位图数组来标记字符是否出现过
bool bitSet[256] = {false};
int result = 0;
int left = 0, right = 0;
while (left < s.length()) {
// 如果右侧字符对应的bitSet被标记为true,说明此字符在X位置重复,需要左侧向前移动,直到将X标记为false
if (bitSet[s[right]]) {
bitSet[s[left]] = false;
left++;
} else {
bitSet[s[right]] = true;
right++;
}
if (result < right - left) {
result = right - left;
}
// 如果左侧加上结果大于等于字符串长度,或者右侧超过字符串长度,跳出循环
if (left + result >= s.length() || right >= s.length()) {
break;
}
}
return result;
}
};
#include
#include
class Solution {
public:
// 解法二:滑动窗口
int lengthOfLongestSubstring(string s) {
if (s.length() == 0) {
return 0;
}
int freq[127] = {0};
int result = 0, left = 0, right = -1;
while (left < s.length()) {
if (right + 1 < s.length() && freq[s[right + 1]] == 0) {
freq[s[right + 1]]++;
right++;
} else {
freq[s[left]]--;
left++;
}
result = max(result, right - left + 1);
}
return result;
}
};
#include
#include
class Solution {
public:
// 解法三:滑动窗口-哈希桶
int lengthOfLongestSubstring(string s) {
int right = 0, left = 0, res = 0;
unordered_map indexes;
while (left < s.length()) {
if (indexes.count(s[left]) && indexes[s[left]] >= right) {
right = indexes[s[left]] + 1;
}
indexes[s[left]] = left;
left++;
res = max(res, left - right);
}
return res;
}
};
Go
Python
Java
C++
There are two sorted arraysnums1andnums2of size m and n respectively.
Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
You may assumenums1andnums2cannot be both empty.
Example 1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
给出两个有序数组,要求找出这两个数组合并以后的有序数组中的中位数。要求时间复杂度为 O(log (m+n))。
这一题最容易想到的办法是把两个数组合并,然后取出中位数。但是合并有序数组的操作是 O(m+n)
的,不符合题意。看到题目给的 log
的时间复杂度,很容易联想到二分搜索。
由于要找到最终合并以后数组的中位数,两个数组的总大小也知道,所以中间这个位置也是知道的。只需要二分搜索一个数组中切分的位置,另一个数组中切分的位置也能得到。为了使得时间复杂度最小,所以二分搜索两个数组中长度较小的那个数组。
关键的问题是如何切分数组 1 和数组 2 。其实就是如何切分数组 1 。先随便二分产生一个 midA
,切分的线何时算满足了中位数的条件呢?即,线左边的数都小于右边的数,即,nums1[midA-1] ≤ nums2[midB] && nums2[midB-1] ≤ nums1[midA]
。如果这些条件都不满足,切分线就需要调整。如果 nums1[midA] < nums2[midB-1]
,说明 midA
这条线划分出来左边的数小了,切分线应该右移;如果 nums1[midA-1] > nums2[midB]
,说明 midA
这条线划分出来左边的数大了,切分线应该左移。经过多次调整以后,切分线总能找到满足条件的解。
假设现在找到了切分的两条线了,数组 1
在切分线两边的下标分别是 midA - 1
和 midA
。数组 2
在切分线两边的下标分别是 midB - 1
和 midB
。最终合并成最终数组,如果数组长度是奇数,那么中位数就是 max(nums1[midA-1], nums2[midB-1])
。如果数组长度是偶数,那么中间位置的两个数依次是:max(nums1[midA-1], nums2[midB-1])
和 min(nums1[midA], nums2[midB])
,那么中位数就是 (max(nums1[midA-1], nums2[midB-1]) + min(nums1[midA], nums2[midB])) / 2
。图示见下图:
各语言版本的解题思路:
Go版本:
Python版本:
Java版本:
C++版本:
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
// 假设 nums1 的长度小
if len(nums1) > len(nums2) {
return findMedianSortedArrays(nums2, nums1) // 如果nums1更长,交换参数位置,递归调用
}
low, high, k, nums1Mid, nums2Mid := 0, len(nums1), (len(nums1)+len(nums2)+1)>>1, 0, 0 // low和high表示二分查找的下界和上界,k表示两数组总长度的中位数索引,nums1Mid和nums2Mid表示nums1和nums2的中位数索引
for low <= high {
// nums1: .................. nums1[nums1Mid-1] | nums1[nums1Mid] ........................
// nums2: .................. nums2[nums2Mid-1] | nums2[nums2Mid] ........................
nums1Mid = low + (high-low)>>1 // 分界限右侧是mid,分界线左侧是mid - 1,二分查找更新nums1的中位数索引
nums2Mid = k - nums1Mid // 根据nums1的中位数索引推算出nums2的中位数索引
if nums1Mid > 0 && nums1[nums1Mid-1] > nums2[nums2Mid] {
// nums1中的中位数划分偏右,需要左移
high = nums1Mid - 1
} else if nums1Mid != len(nums1) && nums1[nums1Mid] < nums2[nums2Mid-1] {
// nums1中的中位数划分偏左,需要右移
low = nums1Mid + 1
} else {
// 找到合适的划分,可以输出结果了
break
}
}
midLeft, midRight := 0, 0
if nums1Mid == 0 {
// nums1划分到左边界,取nums2中位数左边那个数
midLeft = nums2[nums2Mid-1]
} else if nums2Mid == 0 {
// nums2划分到左边界,取nums1中位数左边那个数
midLeft = nums1[nums1Mid-1]
} else {
// 否则取两个中位数左边的最大值
midLeft = max(nums1[nums1Mid-1], nums2[nums2Mid-1])
}
if (len(nums1)+len(nums2))&1 == 1 {
// 如果奇数长度,中位数就是midLeft
return float64(midLeft)
}
if nums1Mid == len(nums1) {
// nums1划分到右边界,取nums2中位数右边那个数
midRight = nums2[nums2Mid]
} else if nums2Mid == len(nums2) {
// nums2划分到右边界,取nums1中位数右边那个数
midRight = nums1[nums1Mid]
} else {
// 否则取两个中位数右边的最小值
midRight = min(nums1[nums1Mid], nums2[nums2Mid])
}
return float64(midLeft+midRight) / 2 // 如果偶数长度,中位数是midLeft和midRight的平均值
}
func max(a int, b int) int {
if a > b {
return a
}
return b
}
func min(a int, b int) int {
if a < b {
return a
}
return b
}
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
# 确保nums1更短
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
# 初始化变量
low, high = 0, len(nums1)
k = (len(nums1) + len(nums2) + 1) // 2
while low <= high:
# 计算nums1中的中间位置
nums1_mid = low + (high - low) // 2
# 计算nums2中的中间位置
nums2_mid = k - nums1_mid
# 判断是否找到正确的划分
if nums1_mid > 0 and nums1[nums1_mid - 1] > nums2[nums2_mid]:
# nums1中位数划分过大,向左移动
high = nums1_mid - 1
elif nums1_mid < len(nums1) and nums1[nums1_mid] < nums2[nums2_mid - 1]:
# nums1中位数划分过小,向右移动
low = nums1_mid + 1
else:
# 找到正确的划分,计算中位数
break
# 计算中位数
mid_left = 0
if nums1_mid == 0:
mid_left = nums2[nums2_mid - 1]
elif nums2_mid == 0:
mid_left = nums1[nums1_mid - 1]
else:
mid_left = max(nums1[nums1_mid - 1], nums2[nums2_mid - 1])
if (len(nums1) + len(nums2)) % 2 == 1:
return mid_left
mid_right = 0
if nums1_mid == len(nums1):
mid_right = nums2[nums2_mid]
elif nums2_mid == len(nums2):
mid_right = nums1[nums1_mid]
else:
mid_right = min(nums1[nums1_mid], nums2[nums2_mid])
return (mid_left + mid_right) / 2
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 假设 nums1 长度小于等于 nums2
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}
int low = 0;
int high = nums1.length;
int k = (nums1.length + nums2.length + 1) / 2;
int nums1Mid = 0;
int nums2Mid = 0;
while (low <= high) {
// nums1: 左子数组.......... nums1[nums1Mid-1] | nums1[nums1Mid]..........右子数组
// nums2: 左子数组.......... nums2[nums2Mid-1] | nums2[nums2Mid]..........右子数组
nums1Mid = (low + high) / 2;
nums2Mid = k - nums1Mid;
if (nums1Mid > 0 && nums1[nums1Mid - 1] > nums2[nums2Mid]) {
// nums1 中的分界线划多了,要向左边移动
high = nums1Mid - 1;
} else if (nums1Mid != nums1.length && nums1[nums1Mid] < nums2[nums2Mid - 1]) {
// nums1 中的分界线划少了,要向右边移动
low = nums1Mid + 1;
} else {
// 找到合适的划分,可以输出结果
break;
}
}
// 计算中位数
int midLeft = 0, midRight = 0;
if (nums1Mid == 0) {
midLeft = nums2[nums2Mid - 1];
} else if (nums2Mid == 0) {
midLeft = nums1[nums1Mid - 1];
} else {
midLeft = Math.max(nums1[nums1Mid - 1], nums2[nums2Mid - 1]);
}
if ((nums1.length + nums2.length) % 2 == 1) {
// 奇数的情况下中位数就是 midLeft
return midLeft;
}
// 偶数的情况下需要计算 midRight
if (nums1Mid == nums1.length) {
midRight = nums2[nums2Mid];
} else if (nums2Mid == nums2.length) {
midRight = nums1[nums1Mid];
} else {
midRight = Math.min(nums1[nums1Mid], nums2[nums2Mid]);
}
return (midLeft + midRight) / 2.0;
}
}
class Solution {
public:
double findMedianSortedArrays(vector& nums1, vector& nums2) {
// 假设nums1数组更短
if (nums1.size() > nums2.size()) {
return findMedianSortedArrays(nums2, nums1); // 如果nums1更长,交换两个数组作为参数,递归调用
}
int low = 0; // 二分查找左边界
int high = nums1.size(); // 二分查找右边界
int k = (nums1.size() + nums2.size() + 1) / 2; // 两个数组总长度的中位数索引
int nums1Mid, nums2Mid; // nums1和nums2数组的中位数索引
while(low <= high) {
nums1Mid = low + (high - low) / 2; // 计算nums1数组的中位数索引
nums2Mid = k - nums1Mid; // 根据nums1的中位数索引计算出nums2中的中位数索引
if (nums1Mid > 0 && nums1[nums1Mid-1] > nums2[nums2Mid]) {
// nums1中的中位数划分偏右,需要左移边界
high = nums1Mid - 1;
} else if (nums1Mid != nums1.size() && nums1[nums1Mid] < nums2[nums2Mid-1]) {
// nums1中的中位数划分偏左,需要右移边界
low = nums1Mid + 1;
} else {
// 找到合适的划分,退出二分查找循环
break;
}
}
int midLeft, midRight; // 两个中位数的值
if (nums1Mid == 0) {
// nums1数组划分到最左,取nums2数组中位数左边的值
midLeft = nums2[nums2Mid-1];
} else if (nums2Mid == 0) {
// nums2数组划分到最左,取nums1数组中位数左边的值
midLeft = nums1[nums1Mid-1];
} else {
// 否则取两个数组中位数左边的最大值
midLeft = max(nums1[nums1Mid-1], nums2[nums2Mid-1]);
}
if ((nums1.size() + nums2.size()) % 2 == 1) {
// 如果总长度是奇数,中位数就是midLeft
return midLeft;
}
if (nums1Mid == nums1.size()) {
// nums1数组划分到最右,取nums2数组中位数右边的值
midRight = nums2[nums2Mid];
} else if (nums2Mid == nums2.size()) {
// nums2数组划分到最右,取nums1数组中位数右边的值
midRight = nums1[nums1Mid];
} else {
// 否则取两个数组中位数右边的最小值
midRight = min(nums1[nums1Mid], nums2[nums2Mid]);
}
return (midLeft + midRight) / 2.0; // 如果总长度是偶数,返回平均值
}
};
四个版本解法所需的基础知识:
Go版本:
Python版本:
Java版本:
C++版本:
Given a strings
, returnthe longest palindromic substringins
.
Example 1:
Input: s = "babad"
Output: "bab"
Note: "aba" is also a valid answer.
Example 2:
Input: s = "cbbd"
Output: "bb"
Example 3:
Input: s = "a"
Output: "a"
Example 4:
Input: s = "ac"
Output: "a"
Constraints:
1 <= s.length <= 1000
s
consist of only digits and English letters (lower-case and/or upper-case),给你一个字符串 s
,找到 s
中最长的回文子串。
dp[i][j]
表示从字符串第 i
个字符到第 j
dp[i][j] = (s[i] == s[j]) && ((j-i < 3) || dp[i+1][j-1])
j - i == 1
的时候,即只有 2 个字符的情况,只需要判断这 2 个字符是否相同即可。j - i == 2
的时候,即只有解法二,中心扩散法。动态规划的方法中,我们将任意起始,终止范围内的字符串都判断了一遍。其实没有这个必要,如果不是最长回文串,无需判断并保存结果。所以动态规划的方法在空间复杂度上还有优化空间。判断回文有一个核心问题是找到“轴心”。如果长度是偶数,那么轴心是中心虚拟的,如果长度是奇数,那么轴心正好是正中心的那个字母。中心扩散法的思想是枚举每个轴心的位置。然后做两次假设,假设最长回文串是偶数,那么以虚拟中心往
2 边扩散;假设最长回文串是奇数,那么以正中心的字符往 2 边扩散。扩散的过程就是对称判断两边字符是否相等的过程。这个方法时间复杂度和动态规划是一样的,但是空间复杂度降低了。时间复杂度
O(n^2),空间复杂度 O(1)。
解法三,滑动窗口。这个写法其实就是中心扩散法变了一个写法。中心扩散是依次枚举每一个轴心。滑动窗口的方法稍微优化了一点,有些轴心两边字符不相等,下次就不会枚举这些不可能形成回文子串的轴心了。不过这点优化并没有优化时间复杂度,时间复杂度
O(n^2),空间复杂度 O(1)。
解法四,马拉车算法。这个算法是本题的最优解,也是最复杂的解法。时间复杂度 O(n),空间复杂度 O(n)。中心扩散法有 2
处有重复判断,第一处是每次都往两边扩散,不同中心扩散多次,实际上有很多重复判断的字符,能否不重复判断?第二处,中心能否跳跃选择,不是每次都枚举,是否可以利用前一次的信息,跳跃选择下一次的中心?马拉车算法针对重复判断的问题做了优化,增加了一个辅助数组,将时间复杂度从
O(n^2) 优化到了 O(n),空间换了时间,空间复杂度增加到 O(n)。
首先是预处理,向字符串的头尾以及每两个字符中间添加一个特殊字符 #
,比如字符串 aaba
处理后会变成 #a#a#b#a#
。那么原先长度为偶数的回文字符串 aa
会变成长度为奇数的回文字符串 #a#a#
,而长度为奇数的回文字符串 aba
会变成长度仍然为奇数的回文字符串 #a#b#a#
,经过预处理以后,都会变成长度为奇数的字符串。**
注意这里的特殊字符不需要是没有出现过的字母,也可以使用任何一个字符来作为这个特殊字符。**
这是因为,当我们只考虑长度为奇数的回文字符串时,每次我们比较的两个字符奇偶性一定是相同的,所以原来字符串中的字符不会与插入的特殊字符互相比较,不会因此产生问题。**
预处理以后,以某个中心扩散的步数和实际字符串长度是相等的。**因为半径里面包含了插入的特殊字符,又由于左右对称的性质,所以扩散半径就等于原来回文子串的长度。
核心部分是如何通过左边已经扫描过的数据推出右边下一次要扩散的中心。这里定义下一次要扩散的中心下标是 i
。如果 i
比 maxRight
要小,只能继续中心扩散。如果 i
比 maxRight
大,这是又分为 3 种情况。三种情况见上图。将上述 3
种情况总结起来,就是 :dp[i] = min(maxRight-i, dp[2*center-i])
,其中,mirror
相对于 center
是和 i
中心对称的,所以它的下标可以计算出来是 2*center-i
。更新完 dp[i]
以后,就要进行中心扩散了。中心扩散以后动态维护最长回文串并相应的更新 center
,maxRight
,并且记录下原始字符串的起始位置 begin
和 maxLen
。
Manacher 算法是解决最长回文子串问题的高效算法,时间复杂度为 O(n),空间复杂度为 O(n)。它利用了回文串的对称性质,采用了中心扩展法的思想,但是通过一些技巧避免了重复计算,从而大大提高了效率。要理解 Manacher 算法,需要掌握以下基础知识:
滑动窗口方法是一种直观但效率较低的解决方案,时间复杂度为 O(n^2),空间复杂度为 O(1)。该方法通过遍历所有可能的中心点,以中心点为起始,不断扩展窗口并判断回文串。要理解滑动窗口方法,需要掌握以下基础知识:
中心扩展法也是一种直观但效率较低的解决方案,时间复杂度为 O(n^2),空间复杂度为 O(1)。这个方法的思想是以每个字符或两个字符为中心,向两边扩展判断回文串。要理解中心扩展法,需要掌握以下基础知识:
动态规划法是一种通用的解决方案,但在解决最长回文子串问题上效率较低,时间复杂度为 O(n^2),空间复杂度为 O(n^2)。这个方法通过构建一个二维数组来记录子问题的解,然后逐步构建更大的回文串。要理解动态规划法,需要掌握以下基础知识:
// 解法一 Manacher's algorithm,时间复杂度 O(n),空间复杂度 O(n)
func longestPalindrome(s string) string {
if len(s) < 2 {
return s
}
newS := make([]rune, 0)
newS = append(newS, '#')
for _, c := range s {
newS = append(newS, c)
newS = append(newS, '#')
}
// dp[i]: 以预处理字符串下标 i 为中心的回文半径(奇数长度时不包括中心)
// maxRight: 通过中心扩散的方式能够扩散的最右边的下标
// center: 与 maxRight 对应的中心字符的下标
// maxLen: 记录最长回文串的半径
// begin: 记录最长回文串在起始串 s 中的起始下标
dp, maxRight, center, maxLen, begin := make([]int, len(newS)), 0, 0, 1, 0
for i := 0; i < len(newS); i++ {
if i < maxRight {
// 这一行代码是 Manacher 算法的关键所在
dp[i] = min(maxRight-i, dp[2*center-i])
}
// 中心扩散法更新 dp[i]
left, right := i-(1+dp[i]), i+(1+dp[i])
for left >= 0 && right < len(newS) && newS[left] == newS[right] {
dp[i]++
left--
right++
}
// 更新 maxRight,它是遍历过的 i 的 i + dp[i] 的最大者
if i+dp[i] > maxRight {
maxRight = i + dp[i]
center = i
}
// 记录最长回文子串的长度和相应它在原始字符串中的起点
if dp[i] > maxLen {
maxLen = dp[i]
begin = (i - maxLen) / 2 // 这里要除以 2 因为有我们插入的辅助字符 #
}
}
return s[begin : begin+maxLen]
}
func min(x, y int) int {
if x < y {
return x
}
return y
}
// 解法二 滑动窗口,时间复杂度 O(n^2),空间复杂度 O(1)
func longestPalindrome(s string) string {
if len(s) == 0 {
return ""
}
left, right, pl, pr := 0, -1, 0, 0
for left < len(s) {
// 移动到相同字母的最右边(如果有相同字母)
for right+1 < len(s) && s[left] == s[right+1] {
right++
}
// 找到回文的边界
for left-1 >= 0 && right+1 < len(s) && s[left-1] == s[right+1] {
left--
right++
}
if right-left > pr-pl {
pl, pr = left, right
}
// 重置到下一次寻找回文的中心
left = (left+right)/2 + 1
right = left
}
return s[pl : pr+1]
}
// 解法三 中心扩散法,时间复杂度 O(n^2),空间复杂度 O(1)
func longestPalindrome(s string) string {
res := ""
for i := 0; i < len(s); i++ {
res = maxPalindrome(s, i, i, res)
res = maxPalindrome(s, i, i+1, res)
}
return res
}
func maxPalindrome(s string, i, j int, res string) string {
sub := ""
for i >= 0 && j < len(s) && s[i] == s[j] {
sub = s[i : j+1]
i--
j++
}
if len(res) < len(sub) {
return sub
}
return res
}
// 解法四 DP,时间复杂度 O(n^2),空间复杂度 O(n^2)
func longestPalindrome(s string) string {
res, dp := "", make([][]bool, len(s))
for i := 0; i < len(s); i++ {
dp[i] = make([]bool, len(s))
}
for i := len(s) - 1; i >= 0; i-- {
for j := i; j < len(s); j++ {
dp[i][j] = (s[i] == s[j]) && ((j-i < 3) || dp[i+1][j-1])
if dp[i][j] && (res == "" || j-i+1 > len(res)) {
res = s[i : j+1]
}
}
}
return res
}
class Solution:
def longestPalindrome(self, s: str) -> str:
if len(s) < 2:
return s
new_s = ['#']
for c in s:
new_s.append(c)
new_s.append('#')
dp = [0] * len(new_s)
max_right = 0
center = 0
max_len = 1
begin = 0
for i in range(len(new_s)):
if i < max_right:
dp[i] = min(max_right - i, dp[2 * center - i])
left = i - (1 + dp[i])
right = i + (1 + dp[i])
while left >= 0 and right < len(new_s) and new_s[left] == new_s[right]:
dp[i] += 1
left -= 1
right += 1
if i + dp[i] > max_right:
max_right = i + dp[i]
center = i
if dp[i] > max_len:
max_len = dp[i]
begin = (i - max_len) // 2
return s[begin:begin + max_len]
class Solution:
# 解法二: 滑动窗口法
def longestPalindrome(self, s: str) -> str:
if len(s) == 0:
return ""
left, right, pl, pr = 0, -1, 0, 0
# 1. 遍历每个字符作为回文中心
while left < len(s):
# 2. 找到相同字符的最右边界
while right + 1 < len(s) and s[left] == s[right + 1]:
right += 1
# 3. 扩展回文边界
while left - 1 >= 0 and right + 1 < len(s) and s[left - 1] == s[right + 1]:
left -= 1
right += 1
# 4. 更新最长回文子串的边界
if right - left > pr - pl:
pl, pr = left, right
# 5. 重置中心,准备下一次寻找
left = (left + right) // 2 + 1
right = left
# 6. 返回结果
return s[pl: pr + 1]
class Solution:
# 解法三: 中心扩散法
def longestPalindrome(self, s: str) -> str:
res = ""
# 1. 以每个字符为中心,寻找最长回文子串
for i in range(len(s)):
res = self.maxPalindrome(s, i, i, res) # 处理奇数长度的回文
res = self.maxPalindrome(s, i, i + 1, res) # 处理偶数长度的回文
# 2. 返回结果
return res
# 辅助函数:在指定范围内寻找最长回文子串
def maxPalindrome(self, s: str, i: int, j: int, res: str) -> str:
sub = ""
while i >= 0 and j < len(s) and s[i] == s[j]:
sub = s[i: j + 1]
i -= 1
j += 1
if len(res) < len(sub):
return sub
return res
class Solution:
# 解法四: 动态规划法
def longestPalindrome(self, s: str) -> str:
res = ""
dp = [[False] * len(s) for _ in range(len(s))]
# 1. 从后往前遍历,填充动态规划表格
for i in range(len(s) - 1, -1, -1):
for j in range(i, len(s)):
dp[i][j] = (s[i] == s[j]) and ((j - i < 3) or dp[i + 1][j - 1])
if dp[i][j] and (not res or j - i + 1 > len(res)):
res = s[i: j + 1]
# 2. 返回结果
return res
class Solution {
public String longestPalindrome(String s) {
if (s.length() < 2) {
return s;
}
// 解法一: Manacher's 算法
StringBuilder newS = new StringBuilder("#");
for (char c : s.toCharArray()) {
newS.append(c);
newS.append('#');
}
// 初始化变量
int[] dp = new int[newS.length()];
int maxRight = 0, center = 0, maxLen = 1, begin = 0;
// Manacher 算法的核心部分
for (int i = 0; i < newS.length(); i++) {
if (i < maxRight) {
dp[i] = Math.min(maxRight - i, dp[2 * center - i]);
}
int left = i - (1 + dp[i]);
int right = i + (1 + dp[i]);
while (left >= 0 && right < newS.length() && newS.charAt(left) == newS.charAt(right)) {
dp[i]++;
left--;
right++;
}
if (i + dp[i] > maxRight) {
maxRight = i + dp[i];
center = i;
}
if (dp[i] > maxLen) {
maxLen = dp[i];
begin = (i - maxLen) / 2;
}
}
// 返回结果
return s.substring(begin, begin + maxLen);
}
}
class Solution {
// 解法二: 滑动窗口法
public String longestPalindrome(String s) {
if (s.length() == 0) {
return "";
}
int left = 0, right = -1, pl = 0, pr = 0;
// 遍历每个字符作为回文中心
while (left < s.length()) {
// 找到相同字符的最右边界
while (right + 1 < s.length() && s.charAt(left) == s.charAt(right + 1)) {
right++;
}
// 扩展回文边界
while (left - 1 >= 0 && right + 1 < s.length() && s.charAt(left - 1) == s.charAt(right + 1)) {
left--;
right++;
}
// 更新最长回文子串的边界
if (right - left > pr - pl) {
pl = left;
pr = right;
}
// 重置中心,准备下一次寻找
left = (left + right) / 2 + 1;
right = left;
}
// 返回结果
return s.substring(pl, pr + 1);
}
}
class Solution {
// 解法三: 中心扩散法
public String longestPalindrome(String s) {
String res = "";
// 以每个字符为中心,寻找最长回文子串
for (int i = 0; i < s.length(); i++) {
res = maxPalindrome(s, i, i, res); // 处理奇数长度的回文
res = maxPalindrome(s, i, i + 1, res); // 处理偶数长度的回文
}
// 返回结果
return res;
}
// 辅助函数:在指定范围内寻找最长回文子串
private String maxPalindrome(String s, int i, int j, String res) {
String sub = "";
while (i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)) {
sub = s.substring(i, j + 1);
i--;
j++;
}
if (sub.length() > res.length()) {
return sub;
}
return res;
}
}
class Solution {
// 解法四: 动态规划法
public String longestPalindrome(String s) {
String res = "";
boolean[][] dp = new boolean[s.length()][s.length()];
// 从后往前遍历,填充动态规划表格
for (int i = s.length() - 1; i >= 0; i--) {
for (int j = i; j < s.length(); j++) {
dp[i][j] = (s.charAt(i) == s.charAt(j)) && ((j - i < 3) || dp[i + 1][j - 1]);
if (dp[i][j] && (res.isEmpty() || j - i + 1 > res.length())) {
res = s.substring(i, j + 1);
}
}
}
// 返回结果
return res;
}
}
class Solution {
public:
string longestPalindrome(string s) {
if (s.length() < 2) {
return s;
}
// 解法一: Manacher's 算法
string newS = "#";
for (char c : s) {
newS += c;
newS += '#';
}
// 初始化变量
vector dp(newS.length(), 0);
int maxRight = 0, center = 0, maxLen = 1, begin = 0;
// Manacher 算法的核心部分
for (int i = 0; i < newS.length(); i++) {
if (i < maxRight) {
dp[i] = min(maxRight - i, dp[2 * center - i]);
}
int left = i - (1 + dp[i]);
int right = i + (1 + dp[i]);
while (left >= 0 && right < newS.length() && newS[left] == newS[right]) {
dp[i]++;
left--;
right++;
}
if (i + dp[i] > maxRight) {
maxRight = i + dp[i];
center = i;
}
if (dp[i] > maxLen) {
maxLen = dp[i];
begin = (i - maxLen) / 2;
}
}
// 返回结果
return s.substr(begin, maxLen);
}
};
class Solution {
public:
// 解法二: 滑动窗口法
string longestPalindrome(string s) {
if (s.length() == 0) {
return "";
}
int left = 0, right = -1, pl = 0, pr = 0;
// 遍历每个字符作为回文中心
while (left < s.length()) {
// 找到相同字符的最右边界
while (right + 1 < s.length() && s[left] == s[right + 1]) {
right++;
}
// 扩展回文边界
while (left - 1 >= 0 && right + 1 < s.length() && s[left - 1] == s[right + 1]) {
left--;
right++;
}
// 更新最长回文子串的边界
if (right - left > pr - pl) {
pl = left;
pr = right;
}
// 重置中心,准备下一次寻找
left = (left + right) / 2 + 1;
right = left;
}
// 返回结果
return s.substr(pl, pr - pl + 1);
}
};
class Solution {
public:
// 解法三: 中心扩散法
string longestPalindrome(string s) {
string res = "";
// 以每个字符为中心,寻找最长回文子串
for (int i = 0; i < s.length(); i++) {
res = maxPalindrome(s, i, i, res); // 处理奇数长度的回文
res = maxPalindrome(s, i, i + 1, res); // 处理偶数长度的回文
}
// 返回结果
return res;
}
// 辅助函数:在指定范围内寻找最长回文子串
string maxPalindrome(string s, int i, int j, string res) {
string sub = "";
while (i >= 0 && j < s.length() && s[i] == s[j]) {
sub = s.substr(i, j - i + 1);
i--;
j++;
}
if (sub.length() > res.length()) {
return sub;
}
return res;
}
};
class Solution {
public:
// 解法四: 动态规划法
string longestPalindrome(string s) {
string res = "";
vector> dp(s.length(), vector(s.length(), false));
// 从后往前遍历,填充动态规划表格
for (int i = s.length() - 1; i >= 0; i--) {
for (int j = i; j < s.length(); j++) {
dp[i][j] = (s[i] == s[j]) && ((j - i < 3) || dp[i + 1][j - 1]);
if (dp[i][j] && (res.empty() || j - i + 1 > res.length())) {
res = s.substr(i, j - i + 1);
}
}
}
// 返回结果
return res;
}
};
The string"PAYPALISHIRING"
is written in a zigzag pattern on a given number of rows like this: (you may want to display
this pattern in a fixed font for better legibility)
P A H N
A P L S I I G
Y I R
And then read line by line:"PAHNAPLSIIGYIR"
Write the code that will take a string and make this conversion given a number of rows:
string convert(string s, int numRows);
Example 1:
Input: s = "PAYPALISHIRING", numRows = 3
Output: "PAHNAPLSIIGYIR"
Example 2:
Input: s = "PAYPALISHIRING", numRows = 4
Output: "PINALSIGYAHRPI"
Explanation:
P I N
A L S I G
Y A H R
P I
Example 3:
Input: s = "A", numRows = 1
Output: "A"
Constraints:
1 <= s.length <= 1000
s
consists of English letters (lower-case and upper-case),','
and'.'
.1 <= numRows <= 1000
将一个给定字符串 s
根据给定的行数 numRows
,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING"
行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"
。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
Go版本:
Python版本:
Java版本:
C++版本:
package leetcode
func convert(s string, numRows int) string { // 定义函数convert,接受字符串s和整数numRows作为参数,返回字符串
matrix, down, up := make([][]byte, numRows, numRows), 0, numRows-2 // 初始化三个变量:二维字节数组matrix,下标变量down和up
for i := 0; i != len(s); { // 使用for循环遍历字符串s
if down != numRows { // 如果down不等于numRows
matrix[down] = append(matrix[down], byte(s[i])) // 将s的第i个字节加入matrix的第down行
down++ // down自增1
i++ // i自增1
} else if up > 0 { // 否则如果up大于0
matrix[up] = append(matrix[up], byte(s[i])) // 将s的第i个字节加入matrix的第up行
up-- // up自减1
i++ // i自增1
} else { // 否则
up = numRows - 2 // 将up赋值为numRows-2
down = 0 // 将down赋值为0
}
}
solution := make([]byte, 0, len(s)) // 初始化字节数组solution
for _, row := range matrix { // 遍历matrix的每一行
for _, item := range row { // 遍历每一行的每个元素
solution = append(solution, item) // 将该元素加入solution
}
}
return string(solution) // 将solution转换成字符串并返回
}
class Solution:
def convert(self, s: str, numRows: int) -> str:
if numRows == 1: # 如果只有1行,不需转换
return s
rows = ["" for _ in range(numRows)] # 初始化长度为numRows的空字符串列表rows表示矩阵
cur_row = 0 # 当前行编号
going_down = False # 索引变化方向标志
for c in s: # 遍历字符串s的每个字符
rows[cur_row] += c # 在对应行末尾添加字符
if cur_row == 0 or cur_row == numRows - 1: # 如果在第一行或最后一行
going_down = not going_down # 改变索引变化方向
if going_down: # 如果向下遍历
cur_row += 1 # 当前行号+1
else: # 如果向上遍历
cur_row -= 1 # 当前行号-1
return "".join(rows) # 拼接rows得到结果并返回
class Solution {
public String convert(String s, int numRows) {
if (numRows == 1) return s; // 如果只有1行,不需转换
char[][] matrix = new char[numRows][s.length()]; // 初始化二维字符数组matrix,行数为numRows,每行长度为s的长度
int curRow = 0; // 当前行号
int dir = -1; // 索引变化方向,-1代表向上
for (int i = 0; i < s.length(); i++) {
matrix[curRow] = addChar(matrix[curRow], s.charAt(i)); // 将当前字符加入 matrix 的当前行
if (curRow == 0 || curRow == numRows - 1) { // 如果在第一行或最后一行
dir = -dir; // 改变索引变化方向
}
curRow += dir; // 当前行号移动
}
StringBuilder result = new StringBuilder();
for (char[] row : matrix) { // 遍历每一行
for (char c : row) {
if (c != 0) result.append(c); // 添加非空字符到结果字符串
}
}
return result.toString(); // 转成字符串并返回
}
private char[] addChar(char[] array, char c) { // 在数组末尾添加一个字符
if (array == null) {
return new char[] {c};
}
int n = array.length;
char[] newArray = Arrays.copyOf(array, n + 1);
newArray[n] = c;
return newArray;
}
}
class Solution {
public:
string convert(string s, int numRows) {
if(numRows == 1) return s; // 只有1行,不需转换
vector rows(numRows); // 初始化长度为numRows的字符串向量rows表示矩阵
int curRow = 0; // 当前行索引
bool goingDown = false; // 索引变化方向标志
for(char c : s) { // 遍历字符串s的每个字符
rows[curRow] += c; // 将字符添加到对应行
if(curRow == 0 || curRow == numRows-1) { // 当前行为第一行或最后一行
goingDown = !goingDown; // 改变索引变化方向
}
if(goingDown) { // 如果向下遍历
curRow++; // 当前行索引+1
} else { // 如果向上遍历
curRow--; // 当前行索引-1
}
}
string ans;
for(string row : rows) { // 遍历每一行
ans += row; // 将行内容拼接到结果字符串
}
return ans; // 返回转换结果
}
};
Go版本:
Python版本:
Java版本:
C++版本:
Given a 32-bit signed integer, reverse digits of an integer.
Example 1:
Input: 123
Output: 321
Example 2:
Input: -123
Output: -321
Example 3:
Input: 120
Output: 21
**Note:**Assume we are dealing with an environment which could only store integers within the 32-bit signed integer range: [−2^31, 2^31 − 1]. For the purpose of this problem, assume that your function returns 0 when the reversed integer overflows.
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。注意:假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31, 2^31 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
Go 版本
Python 版本
Java 版本
C++版本
func reverse(x int) int {
tmp := 0 // 定义一个临时变量tmp,初始化为0
for x != 0 { // 当x不等于0时,进行循环
tmp = tmp*10 + x%10 // 将x对10取余,加到tmp末尾,实现逆序
x = x / 10 // x整除10,去掉末尾数字
}
if tmp > 1<<31-1 || tmp < -(1<<31) { // 检查tmp是否超出int范围
return 0 // 如果超出范围,返回0
}
return tmp // 返回tmp
}
class Solution:
def reverse(self, x: int) -> int:
result = 0 # 结果初始化为0
if x < 0: # 判断x是否为负数
symbol = -1 # 如果x为负数,符号设为-1
x = -x # 将x变正
else:
symbol = 1 # 否则符号为1
while x != 0:
result = result * 10 + x % 10 # 取x的末尾数字,加到result末尾
x = x // 10 # x去掉末尾数字
result = result * symbol # 恢复result的符号
if result >= 2**31 or result < -2**31: # 判断是否越界
return 0
else:
return result
class Solution {
public int reverse(int x) {
int result = 0; // 结果初始化为0
boolean isNegative = x < 0; // 判断x是否为负数
if(isNegative) {
x = -x; // 如果为负数,将x变为正数
}
while(x != 0) {
int pop = x % 10; // x对10取余,获取末位数字
x /= 10; // x除以10,删除末位数字
if (result > Integer.MAX_VALUE/10 || (result == Integer.MAX_VALUE / 10 && pop > 7)) {
return 0; // 检查是否越上界
}
if (result < Integer.MIN_VALUE/10 || (result == Integer.MIN_VALUE / 10 && pop < -8)) {
return 0; // 检查是否越下界
}
result = result * 10 + pop; // 结果乘10后加上末位数字
}
if(isNegative) {
result = -result; // 如果为负数,结果取反
}
return result;
}
}
class Solution {
public:
int reverse(int x) {
int result = 0; // 结果初始化为0
bool isNegative = x < 0; // 判断是否为负数
if (isNegative) {
if (x == INT_MIN) { // 如果是最小值,取反会溢出,直接返回0
return 0;
}
x = -x; // 负数取反使之为正数
}
while (x != 0) {
int pop = x % 10; // 取余取得末尾数字
x /= 10; // 移除末尾数字
if (result > INT_MAX/10 || (result == INT_MAX/10 && pop > INT_MAX%10)) {
return 0; // 检查越界
}
if (result < INT_MIN/10 || (result == INT_MIN/10 && pop < INT_MIN%10)) {
return 0;
}
result = result * 10 + pop; // 组装结果
}
if (isNegative) {
result = -result; // 恢复负号
}
return result;
}
};
Go语言基础知识:
Python基础知识:
Java基础知识:
C++基础知识:
此外,Java和C++版本要注意反转最小值时的溢出问题,需要特判。