2——两数相加
/**
* 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) {
ListNode *head=nullptr,*tail=nullptr;
int carry=0;
while(l1||l2)
{
int n1=l1?l1->val:0;
int n2=l2?l2->val:0;
int sum=n1+n2+carry;
if(!head){
head=tail=new ListNode(sum%10);
}
else {
tail->next=new ListNode(sum%10);
tail=tail->next;
}
carry=sum/10;
if(l1){
l1=l1->next;
}
if(l2){
l2=l2->next;
}
if(carry>0)
{
tail->next=new ListNode(carry);
}
}
return head;
}
};
3——无重复字符的最长字串
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int len=s.size();
int res=0;
int r=0;
unordered_set<char>st;
for(int i=0;i<len;i++)
{
while(r<len&&!st.count(s[r])){
st.insert(s[r]);
r++;
}
res=max(res,r-i);
st.erase(s[i]);
}
return res;
}
};
变量说明
len
: 字符串s
的长度。res
: 最长无重复字符子串的长度,初始化为0。r
: 滑动窗口的右边界(包含),初始化为0。st
: 一个unordered_set
,用于存储当前滑动窗口中的字符,以便快速检查某个字符是否已存在。i
: 滑动窗口的左边界(包含),通过循环遍历字符串的每个字符。
算法逻辑
-
初始化:设置
res
为0,r
为0,并创建一个空的unordered_set
st
。 -
遍历字符串:通过
for
循环遍历字符串s
的每个字符,索引为i
。 -
扩展右边界:在内部
while
循环中,只要r
小于字符串长度且s[r]
不在st
中,就将s[r]
添加到st
中,并将r
向右移动一位。这个过程实际上是在尝试扩展滑动窗口的右边界,以包含更多的字符,同时保持窗口内的字符都是唯一的。 -
更新结果:在每次内部循环结束后(即无法再扩展右边界时),计算当前滑动窗口的长度(
r - i
),并与res
比较,更新res
为较大的值。这是因为我们已经找到了一个无重复字符的子串,并可能更新了最长子串的长度。 -
收缩左边界:在更新
res
之后,我们需要将s[i]
从st
中移除,以准备下一次迭代。这是因为i
即将向右移动,我们需要更新滑动窗口以包含新的字符(s[i+1]
),而移除s[i]
是为了保证窗口内的字符唯一性。 -
继续遍历:
i
向右移动一位,重复上述过程,直到遍历完整个字符串。
23——合并k个升序链表
解题代码
/**
* 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* mergeKLists(vector<ListNode*>& lists) {
// 使用一个最小堆来存储所有链表的头节点,堆中的元素是pair<int, ListNode*>
// 其中int是节点的值,ListNode*是指向节点的指针
auto compare = [](const pair<int, ListNode*>& a, const pair<int, ListNode*>& b) {
return a.first > b.first; // 注意这里是大于号,因为我们想要的是最小堆
};
priority_queue<pair<int, ListNode*>, vector<pair<int, ListNode*>>, decltype(compare)> pq(compare);
// 将所有链表的头节点加入堆中
for (ListNode* list : lists) {
if (list != nullptr) {
pq.push({list->val, list});
}
}
ListNode* dummy = new ListNode(0); // 创建一个虚拟头节点
ListNode* curr = dummy;
// 当堆不为空时,循环进行合并
while (!pq.empty()) {
// 取出堆顶元素(即当前最小值)
pair<int, ListNode*> top = pq.top();
pq.pop();
// 将该节点接到结果链表中
curr->next = top.second;
curr = curr->next;
// 如果该节点有后继节点,则将后继节点也加入堆中
if (curr->next != nullptr) {
pq.push({curr->next->val, curr->next});
}
}
return dummy->next; // 返回虚拟头节点的下一个节点,即合并后的链表头节点
}
};
完整代码(包含头文件)
#include <vector>
#include <queue>
#include <functional>
#include <climits>
using namespace std;
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* mergeKLists(vector<ListNode*>& lists) {
// 使用一个最小堆来存储所有链表的头节点,堆中的元素是pair<int, ListNode*>
// 其中int是节点的值,ListNode*是指向节点的指针
auto compare = [](const pair<int, ListNode*>& a, const pair<int, ListNode*>& b) {
return a.first > b.first; // 注意这里是大于号,因为我们想要的是最小堆
};
priority_queue<pair<int, ListNode*>, vector<pair<int, ListNode*>>, decltype(compare)> pq(compare);
// 将所有链表的头节点加入堆中
for (ListNode* list : lists) {
if (list != nullptr) {
pq.push({list->val, list});
}
}
ListNode* dummy = new ListNode(0); // 创建一个虚拟头节点
ListNode* curr = dummy;
// 当堆不为空时,循环进行合并
while (!pq.empty()) {
// 取出堆顶元素(即当前最小值)
pair<int, ListNode*> top = pq.top();
pq.pop();
// 将该节点接到结果链表中
curr->next = top.second;
curr = curr->next;
// 如果该节点有后继节点,则将后继节点也加入堆中
if (curr->next != nullptr) {
pq.push({curr->next->val, curr->next});
}
}
return dummy->next; // 返回虚拟头节点的下一个节点,即合并后的链表头节点
}
};
24——两两交换链表中的节点
/**
* 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* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return head;
ListNode* v_head = new ListNode();
v_head->next = head;
ListNode* pre = v_head;
ListNode* cur = head;
while (pre->next && pre->next->next) {
pre->next = pre->next->next;
cur->next = pre->next->next;
pre->next->next = cur;
pre = cur;
cur = cur->next;
}
return v_head->next;
}
};
25——k个一组翻转链表
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
// 哨兵节点
ListNode dummy(0, head);
// k 个节点一组
// pre 是上一组的尾节点
ListNode* pre = &dummy;
ListNode* cur = pre;
while (cur) {
// 判断当前组的长度是否大于等于 k
for (int i = 0; i < k && cur; ++i) {
cur = cur->next;
}
if (!cur) {
// 不足 k 个
break;
}
// 逆序当前组
cur = pre->next;
for (int i = 1; i < k && cur && cur->next; ++i) {
ListNode* nxt = cur->next;
cur->next = nxt->next;
nxt->next = pre->next;
pre->next = nxt;
}
// 下一组
pre = cur;
}
return dummy.next;
}
};
26——删除有序数组中的重复项
思路:
这段代码使用两个迭代器:lastUnique
指向最后一个不同的元素,current
遍历整个数组。如果 current
指向的元素与 lastUnique
指向的元素不同,则将 current
指向的元素复制到 lastUnique + 1
的位置,并递增 lastUnique
。这样,在遍历结束后,nums
中从开头到 lastUnique
(不包括 lastUnique
)的部分就是去重后的结果,而 lastUnique - nums.begin() + 1
就是新数组的大小。
注意,这种方法不会改变 nums
中元素的顺序,只会删除重复的元素。
27——移除元素
#include <vector>
class Solution {
public:
int removeElement(std::vector<int>& nums, int val) {
auto index = nums.begin();
while (index != nums.end()) {
if (*index == val) {
index = nums.erase(index); // 删除元素并移动迭代器
} else {
++index; // 如果没有删除,则递增迭代器
}
}
return nums.size(); // 返回剩余元素的数量
}
};
注意:
从 vector
中删除元素时,迭代器 index
会失效,因此不能直接在循环中删除元素并继续递增 index
。
29——两数相除
法一:减法模拟
class Solution {
public:
int divide(int dividend, int divisor) {
int res, count;
int temp;
int flag=0;
if(dividend==INT_MIN&&divisor==-1)
return INT_MAX;
if (dividend == 0)
return 0;
if (divisor == 1)
return dividend;
if (divisor == -1)
return -dividend;
if(dividend>0&&divisor<0){
flag=1;
dividend=-dividend;
}
if(dividend<0&&divisor>0){
flag=1;
divisor=-divisor;
}
if(dividend>0&&divisor>0){
dividend=-dividend;
divisor=-divisor;
}
if(dividend==divisor)
{
if(flag==1)return -1;
else return 1;
}
if(dividend>divisor){
return 0;
}
temp=0;
count=1;
res=0;
while(dividend<=divisor){
temp+=divisor;
dividend-=temp;
res=res+count;
count++;
}
if(dividend>0){
dividend+=temp;
res=res-count+1;
while(dividend<=divisor){
dividend-=divisor;
res++;
}
}
if(flag==1)
res=-res;
return res;
}
};
法二:位运算
class Solution {
public:
int divide(int dividend, int divisor) {
if (divisor == 0) throw std::invalid_argument("divisor cannot be zero");
// 处理溢出情况
if (dividend == INT_MIN && divisor == -1) return INT_MAX;
// 处理符号
bool isNegative = (dividend < 0) ^ (divisor < 0);
dividend = abs(dividend);
divisor = abs(divisor);
int result = 0;
while (dividend >= divisor) {
int temp = divisor;
int factor = 1;
// 通过位运算和加法来模拟除法
while (dividend >= (temp << 1)) {
temp <<= 1;
factor <<= 1;
}
dividend -= temp;
result += factor;
}
return isNegative ? -result : result;
}
};
31——下一个排列
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int i = nums.size() - 2;
int j = nums.size() - 1;
while (i >= 0 && nums[i] >= nums[i + 1])
i--;
if (i >= 0) {
while (j > i && nums[j] <= nums[i])
j--;
swap(nums[i], nums[j]);
sort(nums.begin() + i + 1, nums.end());
}
if(i<0)sort(nums.begin(),nums.end());
}
};
优化代码
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int i = nums.size() - 2; // 初始化i为倒数第二个元素的索引
int j = nums.size() - 1; // 初始化j为最后一个元素的索引
// Step 1: 从右向左找到第一个不满足降序的元素对 (i, i+1)
while (i >= 0 && nums[i] >= nums[i + 1]) {
i--;
}
// 如果i小于0,说明整个数组是降序的(已经是最大排列),需要反转整个数组
if (i >= 0) {
// Step 2: 在i的右侧找到第一个大于nums[i]的元素
while (j > i && nums[j] <= nums[i]) {
j--;
}
// Step 3: 交换nums[i]和nums[j]
swap(nums[i], nums[j]);
// Step 4: 将i+1及其之后的部分进行升序排序(但这里我们不需要真正的排序,只需要反转)
// 因为从i+1到数组末尾的部分原本就是降序的,反转后就变成了升序
reverse(nums.begin() + i + 1, nums.end()); // 使用reverse而不是sort
} else {
// 如果i小于0,说明整个数组已经是降序的,直接反转整个数组得到最小排列
reverse(nums.begin(), nums.end());
}
}
};
分析和注释:
-
初始化索引:
i
初始化为倒数第二个元素的索引,j
初始化为最后一个元素的索引。 -
Step 1:通过从右向左遍历,找到第一个不满足降序的元素对
(i, i+1)
。如果i
小于 0,则整个数组已经是降序的。 -
Step 2:在
i
的右侧找到第一个大于nums[i]
的元素。这是通过从右向左遍历实现的,直到找到一个比nums[i]
大的元素或者j
等于i
。 -
Step 3:交换
nums[i]
和nums[j]
。这一步是为了使i
位置的元素变大,但尽可能小,从而得到下一个排列。 -
Step 4:将
i+1
及其之后的部分进行反转,因为这部分原本就是降序的,反转后就变成了升序。这里应该使用reverse
而不是sort
,因为reverse
更高效,且我们不需要对这部分进行排序,只需要反转其顺序。 -
特殊情况处理:如果
i
小于 0,说明整个数组是降序的,直接反转整个数组即可得到最小排列。
32.最长有效括号
、
class Solution {
public:
int longestValidParentheses(string s) {
stack<int> stk;
stk.push(-1);
int num = 0, max_ = 0;
for (int i = 0; i < s.length(); i++) {
if (s[i] == '(')
stk.push(i);
else if (s[i] == ')') {
stk.pop();
if (stk.empty())
stk.push(i);
else
max_ = max(max_, i - stk.top());
} else
;
}
return max_;
}
};
思路和算法
通过栈,我们可以在遍历给定字符串的过程中去判断到目前为止扫描的子串的有效性,同时能得到最长有效括号的长度。
具体做法是我们始终保持栈底元素为当前已经遍历过的元素中「最后一个没有被匹配的右括号的下标」,这样的做法主要是考虑了边界条件的处理,栈里其他元素维护左括号的下标:
1、对于遇到的每个 ‘(’ ,我们将它的下标放入栈中
2、对于遇到的每个 ‘)’ ,我们先弹出栈顶元素表示匹配了当前右括号:
——如果栈为空,说明当前的右括号为没有被匹配的右括号,我们将其下标放入栈中来更新我们之前提到的「最后一个没有被匹配的右括号的下标」
——如果栈不为空,当前右括号的下标减去栈顶元素即为「以该右括号为结尾的最长有效括号的长度」
我们从前往后遍历字符串并更新答案即可。
需要注意的是,如果一开始栈为空,第一个字符为左括号的时候我们会将其放入栈中,这样就不满足提及的「最后一个没有被匹配的右括号的下标」,为了保持统一,我们在一开始的时候往栈中放入一个值为 −1 的元素。
35——搜索插入位置
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
if (nums[mid] == target) {
return mid; // 找到目标值,直接返回索引
} else if (nums[mid] < target) {
left = mid + 1; // 目标值在右半部分
} else {
right = mid - 1; // 目标值在左半部分或比所有元素都小
}
}
// 循环结束时,left 会指向第一个大于 target 的元素的位置,或者超出数组范围(即 target 比所有元素都大)
// 因此,left 的位置就是 target 应该插入的位置
return left;
}
};
使用了二分查找的思想,它能够在平均情况下以 O(log n) 的时间复杂度找到目标值应该插入的位置。如果目标值存在于数组中,它会返回目标值的索引;如果目标值不存在,它会返回目标值应该插入的位置,即在所有大于目标值的元素中最小的那个元素的左侧。如果目标值比数组中的所有元素都大,则返回数组的长度,即目标值应该插入在数组的末尾。
36——有效的数独
#include <vector>
#include <unordered_set>
#include <string>
using namespace std;
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
// 检查行
for (int i = 0; i < 9; i++) {
unordered_set<char> rowSet;
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
if (rowSet.find(board[i][j]) != rowSet.end()) {
return false;
}
rowSet.insert(board[i][j]);
}
}
}
// 检查列
for (int j = 0; j < 9; j++) {
unordered_set<char> colSet;
for (int i = 0; i < 9; i++) {
if (board[i][j] != '.') {
if (colSet.find(board[i][j]) != colSet.end()) {
return false;
}
colSet.insert(board[i][j]);
}
}
}
// 检查宫格
for (int block = 0; block < 9; block++) {
int startRow = block / 3 * 3;
int startCol = block % 3 * 3;
unordered_set<char> blockSet;
for (int i = startRow; i < startRow + 3; i++) {
for (int j = startCol; j < startCol + 3; j++) {
if (board[i][j] != '.') {
if (blockSet.find(board[i][j]) != blockSet.end()) {
return false;
}
blockSet.insert(board[i][j]);
}
}
}
}
return true;
}
};
代码解释:
-
行检查:遍历每一行,使用一个
unordered_set
来存储已经出现过的字符。如果遇到一个已经存在的字符,则返回false
。 -
列检查:与行检查类似,但这次是遍历每一列。
-
宫格检查:首先计算当前宫格的起始行和起始列,然后遍历该宫格内的所有格子。使用一个
unordered_set
来检查当前宫格内的字符是否重复。
这种方法通过分别检查行、列和宫格来确保每个数字在9x9的棋盘中的每个部分(行、列、宫格)内只出现一次。如果所有检查都通过,则返回true
,表示这是一个有效的数独棋盘。
125——验证回文串
#include <cctype> // 用于 std::tolower
#include <string>
class Solution {
public:
bool isPalindrome(std::string s) {
int i = 0, j = s.length() - 1;
while (i < j) {
// 跳过非字母字符
while (i < j && !isalnum(s[i])) i++;
while (i < j && !isalnum(s[j])) j--;
// 检查两个字符是否相等(忽略大小写)
if (tolower(s[i])!=tolower(s[j])) {
return false;
}
// 移动指针
i++;
j--;
}
return true;
}
};
这段代码定义了一个名为 Solution
的类,其中包含一个公共成员函数 isPalindrome
,用于判断一个给定的字符串 s
是否是回文字符串。回文字符串是指正着读和反着读都一样的字符串,但在这个实现中,它还忽略了非字母数字字符(即空格、标点符号等),并且不区分大小写。
下面是代码的详细分析:
- 头文件包含:
#include <cctype>
:包含了字符处理函数,如isalnum
和tolower
,这些函数用于判断字符是否为字母或数字,以及将大写字母转换为小写字母。#include <string>
:包含了std::string
类的定义,这是 C++ 标准库中用于处理字符串的类。
- 类定义:
class Solution
:定义了一个名为Solution
的类。
- 成员函数:
bool isPalindrome(std::string s)
:这是一个公共成员函数,接受一个std::string
类型的参数s
,并返回一个布尔值,表示s
是否是回文字符串(忽略非字母数字字符和大小写)。
- 实现逻辑:
- 使用两个指针(或索引)
i
和j
,分别指向字符串的开头和结尾。 - 在一个外层
while
循环中,只要i
小于j
,就继续执行循环。 - 在内层
while
循环中,首先跳过所有非字母数字字符(!isalnum(s[i])
和!isalnum(s[j])
),即向前移动i
或向后移动j
,直到找到一个字母或数字字符或两个指针相遇。 - 使用
tolower
函数将当前指向的字符转换为小写(如果它们是字母的话),然后比较这两个字符是否相等。如果不相等,则s
不是回文字符串,函数返回false
。 - 如果字符相等,则同时向前移动
i
和向后移动j
,继续检查下一对字符。 - 如果外层循环完成而没有返回
false
,则说明s
是回文字符串(在忽略非字母数字字符和大小写的情况下),函数返回true
。
- 使用两个指针(或索引)
- 注意事项:
- 这段代码正确地处理了非字母数字字符和大小写不敏感的比较。
- 它假设字符串
s
可以是空的(空字符串被认为是回文字符串)。 - 使用
tolower
函数时,虽然std::tolower
通常期望一个int
类型的参数(通常是unsigned char
转换而来,以避免负值问题),但在这个特定情况下,由于s[i]
和s[j]
已经是char
类型,并且我们期望它们表示有效的字符(即,不是 EOF),因此直接传递它们给tolower
是可以接受的。然而,在更健壮的代码中,使用static_cast<unsigned char>(s[i])
和static_cast<unsigned char>(s[j])
作为tolower
的参数会更安全。
综上所述,这段代码是一个有效的实现,用于判断一个字符串(忽略非字母数字字符和大小写)是否是回文字符串。