【leetcode】腾讯精选练习 50 题(更新中)
2. 两数相加
https://leetcode-cn.com/problems/add-two-numbers/
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
题目思路
总共三个数需要相加,l1取的节点的值,l2取的节点的值,以及上一轮的进位。
然后算得给下一轮的进位,并将当前值只保留个位。
注意:最后如果只剩一个进位,而l1和l2没有取到值,这时候也是要输出一个节点的。
时间和空间复杂度均为O(max(m,n))。
代码
/**
* 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) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int save = 0;
ListNode* rst = new ListNode(0);
ListNode* cur = rst;
do {
int left = 0;
int right = 0;
int all = 0;
if (l1 != nullptr) {
left = l1->val;
l1 = l1->next;
}
if (l2 != nullptr) {
right = l2->val;
l2 = l2->next;
}
all = left + right + save;
// cout << all << endl;
save = all / 10;
all = all % 10;
ListNode* temp = new ListNode(all);
cur->next = temp;
cur = cur->next;
} while (l1 != nullptr || l2 != nullptr);
if (save != 0) {
ListNode* temp = new ListNode(save);
cur->next = temp;
cur = cur->next;
}
return rst->next;
}
};
4. 寻找两个正序数组的中位数
https://leetcode-cn.com/problems/median-of-two-sorted-arrays/
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。
进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?
题目思路
首先是判断中位数的位置,总共为奇数个的话,是最中间的一个数,偶数的话,是最中间的两个数求平均。
偶数个的时候,划分两个数组,左边总共(m+n)/2个数,奇数个的时候,左边总共(m+n+1)/2个数(中位数人为定义放左边)。统一奇偶写法为(m+n+1)/2。
最终需要确定划分的位置即可,为了方便处理边界值,将较短的一个数组设为nums1,则nums1的划分确定,根据上述,nums2的划分也随之确定。
最终划分确定需要满足交叉判断条件,nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i],注意考虑边界条件,具体在代码中有体现。
注意:二分查找中当看到left = i时,要考虑上取整,left = i-1时,不用上取整。
代码
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
// 保持nums1更短
if (nums1.size() > nums2.size()) {
return findMedianSortedArrays(nums2, nums1);
}
int m = nums1.size();
int n = nums2.size();
// 偶数 (m+n)/2 == (m+n+1)/2
// 奇数 (m+n+1)/2 中位数在左边
int leftTotal = (m + n + 1) / 2;
// 二分查找
// 交叉判断条件:
// nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i]
int left = 0;
int right = m;
while (left < right) { // 最终left == right退出,只有一个值,因此交叉判断条件取一个即可
int i = left + (right - left + 1) / 2;
int j = leftTotal - i;
if (nums1[i-1] > nums2[j]) { // i-1不会溢出,因为有上取整,i != 0
right = i - 1;
} else {
// 特殊情况:[left(i), right(i+1)], 区间无法缩小
// 因此i定义的时候,需要上取整(+1)
left = i;
}
}
int i = left;
int j = leftTotal - i;
int leftNums1 = (i == 0 ? INT_MIN : nums1[i-1]);
int rightNums1 = (i == m ? INT_MAX : nums1[i]);
int leftNums2 = (j == 0 ? INT_MIN : nums2[j-1]);
int rightNums2 = (j == n ? INT_MAX : nums2[j]);
if ((m + n) % 2 == 1) {
return max(leftNums1, leftNums2);
} else {
return (max(leftNums1, leftNums2) + min(rightNums1, rightNums2)) / 2.0;
}
}
};
5. 最长回文子串
https://leetcode-cn.com/problems/longest-palindromic-substring/
给你一个字符串 s,找到 s 中最长的回文子串。
解题思路
中心扩散法:遍历字符,以当前字符向两边扩散,找到最长的回文串。时间复杂度O(n^2),空间复杂度O(1)。枚举子串个数O(n)。
动态规划法(空间换时间):保存状态dp[i][j],即从下标i到下标j的子串是否为回文。时间复杂度O(n2),空间复杂度O(n2)。枚举子串个数O(n^2),耗时更长。
Manacher算法:结合中心扩散法和动态规划,时间复杂度O(n),空间复杂度O(n)。(面试不用掌握这个方法)
当在位置 i 开始进行中心拓展时,我们可以先找到 i 关于 j 的对称点 2 * j - i。
那么如果点 2 * j - i 的臂长等于 n,我们就可以知道,点 i 的臂长至少为 min(j + length - i, n)。
那么我们就可以直接跳过 i 到 i + min(j + length - i, n) 这部分,从 i + min(j + length - i, n) + 1 开始拓展。
统一奇偶字符串的技巧:在字符中间插入#。
代码
class Solution {
public:
int palindromeLen(string s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
left -= 1;
right += 1;
}
return right-left-1;
}
string longestPalindrome(string s) {
int max_index = 0;
int max_len = 0;
string ret;
for (int i = 0; i < s.size(); i++) {
int temp_len = max(palindromeLen(s, i, i), palindromeLen(s, i, i+1));
if (temp_len > max_len) {
max_len = temp_len;
max_index = i;
}
}
if (max_len % 2 == 0) {
ret = s.substr(max_index+1 - max_len/2, max_len);
} else {
ret = s.substr(max_index - max_len/2, max_len);
}
return ret;
}
};
7. 整数反转
https://leetcode-cn.com/problems/reverse-integer/
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
解题思路
题目比较简单,不过要注意判断,避免溢出。
时间复杂度:O(log(x))。
空间复杂度:O(1)。
代码
class Solution {
public:
int reverse(int x) {
int rev = 0;
while(x != 0) {
int temp = x % 10;
x /= 10;
if (rev > INT_MAX/10 || (rev == INT_MAX / 10 && temp > 7)) return 0;
if (rev < INT_MIN/10 || (rev == INT_MIN / 10 && temp < -8)) return 0;
rev = rev * 10 + temp;
}
return rev;
}
};
8. 字符串转换整数 (atoi)
https://leetcode-cn.com/problems/string-to-integer-atoi/
请你来实现一个 atoi 函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。接下来的转化规则如下:
如果第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。
假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
注意:
本题中的空白字符只包括空格字符 ' ' 。
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−2^31, 2^31 − 1]。如果数值超过这个范围,请返回 2^31 − 1 或 −2^31。
解题思路
代码
class Solution {
public:
int myAtoi(string s) {
int ret = 0;
int start = true;
int flag = 1;
for (int i = 0; i < s.size(); i++) {
if (start && s[i] == ' ')
continue;
if (start && (s[i] == '-' || s[i] == '+')) {
if (s[i] == '-')
flag = -1;
start = false;
continue;
}
if (s[i] >= '0' && s[i] <= '9') {
start = false;
int temp = flag * (s[i]-'0');
if (ret > INT_MAX/10 || (ret == INT_MAX/10 && temp > 7)) return INT_MAX;
if (ret < INT_MIN/10 || (ret == INT_MIN/10 && temp < -8)) return INT_MIN;
ret = ret*10 + temp;
} else {
break;
}
}
return ret;
}
};
9. 回文数
https://leetcode-cn.com/problems/palindrome-number/
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
你能不将整数转为字符串来解决这个问题吗?
解题思路
负数不满足条件,先过滤掉。然后反转数字,判断是否相等。(注意可能溢出)
代码
class Solution {
public:
bool isPalindrome(int x) {
if (x < 0) {
return false;
}
int reverse = 0;
int x_cp = x;
while (x != 0) {
int temp = x % 10;
x /= 10;
if (reverse > INT_MAX/10 || ((reverse == INT_MAX/10) && temp > 7)) return false;
reverse = reverse * 10 + temp;
}
if (reverse == x_cp) {
return true;
}
return false;
}
};