LeetCode刷题笔记
简单题
2022/1/25
1688. 比赛中的配对次数
给你一个整数 n
,表示比赛中的队伍数。比赛遵循一种独特的赛制:
- 如果当前队伍数是 偶数 ,那么每支队伍都会与另一支队伍配对。总共进行
n / 2
场比赛,且产生n / 2
支队伍进入下一轮。 - 如果当前队伍数为 奇数 ,那么将会随机轮空并晋级一支队伍,其余的队伍配对。总共进行
(n - 1) / 2
场比赛,且产生(n - 1) / 2 + 1
支队伍进入下一轮。
返回在比赛中进行的配对次数,直到决出获胜队伍为止。
示例 1:
输入:n = 7
输出:6
解释:比赛详情:
- 第 1 轮:队伍数 = 7 ,配对次数 = 3 ,4 支队伍晋级。
- 第 2 轮:队伍数 = 4 ,配对次数 = 2 ,2 支队伍晋级。
- 第 3 轮:队伍数 = 2 ,配对次数 = 1 ,决出 1 支获胜队伍。
总配对次数 = 3 + 2 + 1 = 6
示例 2:
输入:n = 14
输出:13
解释:比赛详情:
- 第 1 轮:队伍数 = 14 ,配对次数 = 7 ,7 支队伍晋级。
- 第 2 轮:队伍数 = 7 ,配对次数 = 3 ,4 支队伍晋级。
- 第 3 轮:队伍数 = 4 ,配对次数 = 2 ,2 支队伍晋级。
- 第 4 轮:队伍数 = 2 ,配对次数 = 1 ,决出 1 支获胜队伍。
总配对次数 = 7 + 3 + 2 + 1 = 13
解题思路:
每进行一场比赛都会淘汰一位,要产生冠军,就要淘汰掉 n-1个人。
代码 :
int numberOfMatches(int n){
return n-1;
}
1. 两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
解题思路:暴力破解,逐个判断两数相加之和是否等于target。
代码 :
int* twoSum(int* nums, int numsSize, int target, int* returnSize){
int i,j;
int *result = (int *)malloc(sizeof(int)*2);
*returnSize = 2;
for(int i = 0; i < numsSize - 1; i++){
for(int j = i + 1; j < numsSize; j++){
if(nums[i] + nums[j] == target){
result[0] = i;
result[1] = j;
return result;
}
}
}
return result;
}
9. 回文数
给你一个整数 x
,如果 x
是一个回文整数,返回 true
;否则,返回 false
。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121
是回文,而 123
不是。
示例 1:
输入:x = 121
输出:true
示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
示例 4:
输入:x = -101
输出:false
代码:
bool isPalindrome(int x){
// 特殊情况:
// 如上所述,当 x < 0 时,x 不是回文数。
// 同样地,如果数字的最后一位是 0,为了使该数字为回文,
// 则其第一位数字也应该是 0
// 只有 0 满足这一属性
if (x < 0 || (x % 10 == 0 && x != 0)) {
return false;
}
int revertedNumber = 0;
while (x > revertedNumber) {
revertedNumber = revertedNumber * 10 + x % 10;
x /= 10;
}
// 当数字长度为奇数时,我们可以通过 revertedNumber/10 去除处于中位的数字。
// 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,revertedNumber = 123,
// 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
return x == revertedNumber || x == revertedNumber / 10;
}
2022/1/27
13. 罗马数字转整数
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2
写做 II
,即为两个并列的 1 。12
写做 XII
,即为 X
+ II
。 27
写做 XXVII
, 即为 XX
+ V
+ II
。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII
,而是 IV
。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX
。这个特殊的规则只适用于以下六种情况:
I
可以放在V
(5) 和X
(10) 的左边,来表示 4 和 9。X
可以放在L
(50) 和C
(100) 的左边,来表示 40 和 90。C
可以放在D
(500) 和M
(1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。
示例 1:
输入: s = "III"
输出: 3
示例 2:
输入: s = "IV"
输出: 4
示例 3:
输入: s = "IX"
输出: 9
示例 4:
输入: s = "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: s = "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
解题思路:
- 正常情况下,左边的数值 > 右边数值,依次从左到右相加即可。
- 例如 XXVII = X + X + V + I + I = 10 + 10 + 5 +1 + 1 = 27.
- 若存在小的数字在大的数字的左边的情况,根据规则需要减去小的数字。对于这种情况,我们也可以将每个字符视作一个单独的值,若一个数字右侧的数字比它大,则将该数字的符号取反。
- 例如 XIV 可视作 X - I + V =10−1+5=14。
代码
int romanToInt(char * s){
int symbolValues[26];
symbolValues['I' - 'A'] = 1;
symbolValues['V' - 'A'] = 5;
symbolValues['X' - 'A'] = 10;
symbolValues['L' - 'A'] = 50;
symbolValues['C' - 'A'] = 100;
symbolValues['D' - 'A'] = 500;
symbolValues['M' - 'A'] = 1000;
int ans = 0;
int length = strlen(s);
for(int i = 0; i < length; ++i){
int value = symbolValues[s[i] - 'A'];
if(i < length-1 && value < symbolValues[s[i + 1] - 'A']){
ans -= value;
}
else{
ans += value;
}
}
return ans;
}
14. 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
示例 1:
输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:
输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。
解题思路:
- 进行纵向对比,将数组中字符串的每一位和第一个字符串进行逐一比较,将不相等的那一位置’\0’,输出strs[0]即可。
代码:
char * longestCommonPrefix(char ** strs, int strsSize){
if(strsSize == 0){
return NULL;
}
for(int i = 0; i < strlen(strs[0]); ++i){
for(int j = 0; j < strsSize; j++ ){
if(strs[j][i] != strs[0][i]){ //不相等时结束
strs[0][i] = '\0';
return strs[0];
}
}
}
return strs[0]; //第一个字符串是最长公共前缀
}
20. 有效的括号
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
示例 4:
输入:s = "([)]"
输出:false
示例 5:
输入:s = "{[]}"
输出:true
解题思路
-
判断括号的有效性可以使用「栈」这一数据结构来解决。
我们遍历给定的字符串 ss。当我们遇到一个左括号时,我们会期望在后续的遍历中,有一个相同类型的右括号将其闭合。由于后遇到的左括号要先闭合,因此我们可以将这个左括号放入栈顶。
当我们遇到一个右括号时,我们需要将一个相同类型的左括号闭合。此时,我们可以取出栈顶的左括号并判断它们是否是相同类型的括号。如果不是相同的类型,或者栈中并没有左括号,那么字符串 s无效,返回False。为了快速判断括号的类型,我们可以使用哈希表存储每一种括号。哈希表的键为右括号,值为相同类型的左括号。
在遍历结束后,如果栈中没有左括号,说明我们将字符串 s中的所有左括号闭合,返回True,否则返回False。
注意到有效字符串的长度一定为偶数,因此如果字符串的长度为奇数,我们可以直接返回False,省去后续的遍历判断过程。
代码:
char pairs(char a) {
if (a == '}') return '{'; //遇到右括号,返回左括号
if (a == ']') return '[';
if (a == ')') return '(';
return 0;
}
bool isValid(char* s) {
int n = strlen(s);
if (n % 2 == 1) { //奇数一定不能匹配
return false;
}
int stk[n + 1], top = 0; //初始化栈
for (int i = 0; i < n; i++) {
char ch = pairs(s[i]);
if (ch) {
if (top == 0 || stk[top - 1] != ch) { //栈空或者不匹配
return false;
}
top--;
} else {
stk[top++] = s[i]; //入栈
}
}
return top == 0; //全部出栈,即匹配
}
21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
解题思路:
- 建立新的链表头结点
- p1,p2,p3指向list1,list2,list3中元素
- 若list1,list2其中之一为NULL,直接将其赋给list3
- 都不为空则按大小将其依次链接到list3,最后将剩余的元素连接到list3的末尾
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode* list3 = (struct ListNode *)malloc(sizeof(struct ListNode));
struct ListNode* p1 = list1;
struct ListNode* p2 = list2;
struct ListNode* p3 = list3;
if(p1 == NULL)
return list2;
if(p2 == NULL)
return list1;
while(p1 && p2){
if(p1->val < p2->val){
p3->next = p1;
p1 = p1 -> next;
}
else{
p3->next = p2;
p2 = p2 -> next;
}
p3 = p3 -> next;
}
if(p1 != NULL)
p3->next = p1;
else
p3->next = p2;
list3 = list3 -> next;
return list3;
}
2022/1/28
26. 删除有序数组中的重复项
给你一个有序数组 nums
,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以**「引用」**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例 1:
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
解题思路
- 将数组中不重复的元素依次向前挪动,用delete记录需要删除的元素的个数
代码:
int removeDuplicates(int* nums, int numsSize){
int delete = 0; //记录删除元素个数
for(int i = 1; i < numsSize; i++){
if(nums[i - 1] == nums[i]){ //相邻两个元素相同,删除后者
delete++;
}
else{
nums[i - delete] = nums[i]; //将不重复元素挪动到正确位置
}
}
return numsSize - delete; //修改后数组元素的长度
}
27. 移除元素(做法同26题)
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以**「引用」**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
代码:
int removeElement(int* nums, int numsSize, int val){
int delete = 0;
for(int i = 0; i < numsSize; i++){
if(nums[i] == val){
delete++;
}
else{
nums[i - delete] = nums[i];
}
}
return numsSize - delete;
}
28. 实现 strStr()
实现 strStr() 函数。
给你两个字符串 haystack
和 needle
,请你在 haystack
字符串中找出 needle
字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1
。
说明:
当 needle
是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle
是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。
示例 1:
输入:haystack = "hello", needle = "ll"
输出:2
示例 2:
输入:haystack = "aaaaa", needle = "bba"
输出:-1
示例 3:
输入:haystack = "", needle = ""
输出:0
解法一:BF算法(简单模式匹配算法)
int strStr(char * haystack, char * needle){
int i = 0, j = 0;
if(strlen(needle) == 0){
return 0;
}
while(i < strlen(haystack) && j < strlen(needle)){
if(haystack[i] != needle[j]){
i = i - j + 1;
j = 0;
}
else{
i++;
j++;
}
}
if(j >= strlen(needle))
return i - strlen(needle);
else
return -1;
}
解法二:KMP算法
int strStr(char* haystack, char* needle) {
int n = strlen(haystack), m = strlen(needle);
if (m == 0) {
return 0;
}
int pi[m];
pi[0] = 0;
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && needle[i] != needle[j]) {
j = pi[j - 1];
}
if (needle[i] == needle[j]) {
j++;
}
pi[i] = j;
}
for (int i = 0, j = 0; i < n; i++) {
while (j > 0 && haystack[i] != needle[j]) {
j = pi[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
示例 4:
输入: nums = [1,3,5,6], target = 0
输出: 0
示例 5:
输入: nums = [1], target = 0
输出: 0
解题思路:
- 要求时间复杂度为
O(log n)
,采用二分查找实现
代码:
int searchInsert(int* nums, int numsSize, int target){
int low = 0, high = numsSize - 1;
while(low <= high){
int mid = (low + high) / 2;
if(nums[mid] == target)
return mid;
if(nums[mid] > target)
high = mid - 1;
else
low = mid + 1;
}
return high + 1;
}
53. 最大子数组和
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
解法一:贪心算法
int maxSubArray(int* nums, int numsSize){ //贪心算法
int cur_sum = nums[0], max_sum = nums[0] ;
if(nums == NULL)
return 0;
for(int i = 1; i < numsSize; i++){
if(nums[i] + cur_sum < nums[i]){ //当前位置之前的和小于0,丢弃之前项
cur_sum = nums[i];
}else{
cur_sum += nums[i]; //大于0加上之前的和
}
if(cur_sum > max_sum)
max_sum = cur_sum;
}
return max_sum;
}
解法二:动态规划
//若前一个元素大于0,加到当前元素上
int maxSubArray(int* nums, int numsSize){ //动态规划
for(int i = 1; i < numsSize; i++){ //修改nums数组,前一个元素大于0,则加到当前元素
if(nums[i - 1] > 0)
nums[i] += nums[i - 1];
}
int max = nums[0];
for(int j = 1; j < numsSize; j++){ //遍历数组,找出最大值
if(nums[j] > max)
max = nums[j];
}
return max;
}
58. 最后一个单词的长度
给你一个字符串 s
,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中最后一个单词的长度。
单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。
示例 1:
输入:s = "Hello World"
输出:5
示例 2:
输入:s = " fly me to the moon "
输出:4
示例 3:
输入:s = "luffy is still joyboy"
输出:6
解题思路:
- 先从后往前逆序找到字符串中最后一个字母,该字母索引记为 pos
- 从pos处从后往前找,直到第一次遇到空格为止,并依次记录单词中字母的个数,即最后一个单词长度。
代码:
int lengthOfLastWord(char * s){
int pos = strlen(s) - 1; //记录从后起第一个不为空格的字母的位置
for(int i = pos; i >= 0; i--){
if(s[i] != ' '){
pos = i;
break;
}
}
int length = 0;
for(int i = pos; i >= 0; i--){ //计算最后一个单词长度
if(s[i] != ' '){
length++;
}else{
break;
}
}
return length;
}