力扣 练习

2——两数相加

1. 两数之和

简单

/**
 * 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——无重复字符的最长字串

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: 滑动窗口的左边界(包含),通过循环遍历字符串的每个字符。

算法逻辑

  1. 初始化:设置res为0,r为0,并创建一个空的unordered_set st

  2. 遍历字符串:通过for循环遍历字符串s的每个字符,索引为i

  3. 扩展右边界:在内部while循环中,只要r小于字符串长度且s[r]不在st中,就将s[r]添加到st中,并将r向右移动一位。这个过程实际上是在尝试扩展滑动窗口的右边界,以包含更多的字符,同时保持窗口内的字符都是唯一的。

  4. 更新结果:在每次内部循环结束后(即无法再扩展右边界时),计算当前滑动窗口的长度(r - i),并与res比较,更新res为较大的值。这是因为我们已经找到了一个无重复字符的子串,并可能更新了最长子串的长度。

  5. 收缩左边界:在更新res之后,我们需要将s[i]st中移除,以准备下一次迭代。这是因为i即将向右移动,我们需要更新滑动窗口以包含新的字符(s[i+1]),而移除s[i]是为了保证窗口内的字符唯一性。

  6. 继续遍历i向右移动一位,重复上述过程,直到遍历完整个字符串。

23——合并k个升序链表

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——两两交换链表中的节点

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个一组翻转链表

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——删除有序数组中的重复项

26. 删除有序数组中的重复项

简单

思路:

这段代码使用两个迭代器:lastUnique 指向最后一个不同的元素,current 遍历整个数组。如果 current 指向的元素与 lastUnique 指向的元素不同,则将 current 指向的元素复制到 lastUnique + 1 的位置,并递增 lastUnique。这样,在遍历结束后,nums 中从开头到 lastUnique(不包括 lastUnique)的部分就是去重后的结果,而 lastUnique - nums.begin() + 1 就是新数组的大小。

注意,这种方法不会改变 nums 中元素的顺序,只会删除重复的元素。

27——移除元素

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——两数相除

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——下一个排列

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());  
        }  
    }  
};

分析和注释

  1. 初始化索引i 初始化为倒数第二个元素的索引,j 初始化为最后一个元素的索引。

  2. Step 1:通过从右向左遍历,找到第一个不满足降序的元素对 (i, i+1)。如果 i 小于 0,则整个数组已经是降序的。

  3. Step 2:在 i 的右侧找到第一个大于 nums[i] 的元素。这是通过从右向左遍历实现的,直到找到一个比 nums[i] 大的元素或者 j 等于 i

  4. Step 3:交换 nums[i] 和 nums[j]。这一步是为了使 i 位置的元素变大,但尽可能小,从而得到下一个排列。

  5. Step 4:将 i+1 及其之后的部分进行反转,因为这部分原本就是降序的,反转后就变成了升序。这里应该使用 reverse 而不是 sort,因为 reverse 更高效,且我们不需要对这部分进行排序,只需要反转其顺序。

  6. 特殊情况处理:如果 i 小于 0,说明整个数组是降序的,直接反转整个数组即可得到最小排列。

 32.最长有效括号

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;  
    }  
};

代码解释:

  1. 行检查:遍历每一行,使用一个unordered_set来存储已经出现过的字符。如果遇到一个已经存在的字符,则返回false

  2. 列检查:与行检查类似,但这次是遍历每一列。

  3. 宫格检查:首先计算当前宫格的起始行和起始列,然后遍历该宫格内的所有格子。使用一个unordered_set来检查当前宫格内的字符是否重复。

这种方法通过分别检查行、列和宫格来确保每个数字在9x9的棋盘中的每个部分(行、列、宫格)内只出现一次。如果所有检查都通过,则返回true,表示这是一个有效的数独棋盘。

125——验证回文串

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 是否是回文字符串。回文字符串是指正着读和反着读都一样的字符串,但在这个实现中,它还忽略了非字母数字字符(即空格、标点符号等),并且不区分大小写。

下面是代码的详细分析:

  1. 头文件包含
    • #include <cctype>:包含了字符处理函数,如 isalnum 和 tolower,这些函数用于判断字符是否为字母或数字,以及将大写字母转换为小写字母。
    • #include <string>:包含了 std::string 类的定义,这是 C++ 标准库中用于处理字符串的类。
  2. 类定义
    • class Solution:定义了一个名为 Solution 的类。
  3. 成员函数
    • bool isPalindrome(std::string s):这是一个公共成员函数,接受一个 std::string 类型的参数 s,并返回一个布尔值,表示 s 是否是回文字符串(忽略非字母数字字符和大小写)。
  4. 实现逻辑
    • 使用两个指针(或索引)i 和 j,分别指向字符串的开头和结尾。
    • 在一个外层 while 循环中,只要 i 小于 j,就继续执行循环。
    • 在内层 while 循环中,首先跳过所有非字母数字字符(!isalnum(s[i]) 和 !isalnum(s[j])),即向前移动 i 或向后移动 j,直到找到一个字母或数字字符或两个指针相遇。
    • 使用 tolower 函数将当前指向的字符转换为小写(如果它们是字母的话),然后比较这两个字符是否相等。如果不相等,则 s 不是回文字符串,函数返回 false
    • 如果字符相等,则同时向前移动 i 和向后移动 j,继续检查下一对字符。
    • 如果外层循环完成而没有返回 false,则说明 s 是回文字符串(在忽略非字母数字字符和大小写的情况下),函数返回 true
  5. 注意事项
    • 这段代码正确地处理了非字母数字字符和大小写不敏感的比较。
    • 它假设字符串 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 的参数会更安全。

综上所述,这段代码是一个有效的实现,用于判断一个字符串(忽略非字母数字字符和大小写)是否是回文字符串。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值