猛攻代码随想录;hot100 day16、17、18 | 26届暑期实习

代码随想录

数组

二分查找

左右边界,取中间位置比较,小了右边界等于中间位置大了左边届等于中间位置加一

移除元素:双指针

同时从前向后,一个for循环快指针找不符合目标值的赋予慢指针,慢指针++,返回慢指针大小

有序数组的平方:双指针

一左一右因为有序所以左指针和右指针比较,大的放最后,++或--后继续比较,从后往前放

长度最小的子数组:双指针滑窗

同时从前往后,一个for循环快指针边走边累加直到超过目标值,while循环判断大等于目标值计算两者之间序列长度,与之前的取最小值,慢指针++的同时sum减去看是否还满足

螺旋矩阵

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> results;
        
        // 检查矩阵是否为空
        if (matrix.size() == 0 || matrix[0].size() == 0) {
            return results;
        }
        
        int rows = matrix.size();
        int columns = matrix[0].size();
        int startx = 0, starty = 0;
        int loop = min(rows, columns) / 2;
        int i, j;
        int offset = 1;
        
        while (loop--) {
            i = startx;
            j = starty;
            
            // 上侧:从左到右
            for (j = starty; j < columns - offset; j++) {
                results.push_back(matrix[i][j]);
            }
            
            // 右侧:从上到下
            for (i = startx; i < rows - offset; i++) {
                results.push_back(matrix[i][j]);
            }
            
            // 下侧:从右到左
            for (; j > starty; j--) {
                results.push_back(matrix[i][j]);
            }
            
            // 左侧:从下到上
            for (; i > startx; i--) {
                results.push_back(matrix[i][j]);
            }
            
            startx++;
            starty++;
            offset++;
        }
        
        // 处理中心元素(如果需要)
        if (min(rows, columns) % 2 == 1) {
            int mid = min(rows, columns) / 2;
            
            // 对于奇数大小的正方形矩阵
            if (rows == columns) {
                results.push_back(matrix[mid][mid]);
            } 
            // 对于长方形矩阵
            else if (rows > columns) {
                for (i = mid; i <= rows - mid - 1; i++) {
                    results.push_back(matrix[i][mid]);
                }
            } else {
                for (j = mid; j <= columns - mid - 1; j++) {
                    results.push_back(matrix[mid][j]);
                }
            }
        }
        
        return results;
    }
};

螺旋矩阵II

二维数组,循环圈数和中心点坐标等于n / 2,每次循环右边界缩减值加一,左闭右开,while循环次数,顺时针绕圈赋值,n为奇数中心点单独赋值

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0));
        int startx = 0, starty = 0; // 循环起始位置
        int loop = n / 2; // 循环圈数
        int mid = n / 2; // 中心位置
        int count = 1; // 赋值
        int offset = 1; // 控制每条边遍历长度
        int i, j;
        while (loop--){
            i = startx;
            j = starty;
            
            // 填充上行 左闭右开
            for(j; j < n - offset; j++){
                res[i][j] = count++;
            }

            // 填充右行 左闭右开
            for(i; i < n - offset; i++){
                res[i][j] = count++;
            }

            // 填充下行 左闭右开
            for(j; j > starty; j--){
                res[i][j] = count++;
            }

            //填充左行 左闭右开
            for(i; i > startx; i--){
                res[i][j] = count++;
            }

            startx++;
            starty++;
            offset++;
        }
        if (n % 2) res[mid][mid] = count;
        return res;

    }
};

区间和:前缀和

另外定义一个前缀和数组,要求的区间和就是两个前缀和数组相减,前面的前缀和要往前一位

开发商购买土地:类前缀和

类似前缀和,分别统计每行/每列结束的时候,当前前缀和与剩余和之间的差值,找到最小值

移动零:双指针

两个指针一起从0往右,遇到非零保存给左指针,最后再从左指针开始给零

盛最多水的容器:双指针

一左一右,高度用最小值,长乘高取最大值,左边高度小左边++,否则右边--

接雨水:双指针

一个记录左边最大值,一个记录右边最大值

class Solution {
public:
    int trap(vector<int>& height) {
        int result = 0;
        int left = 0, right = height.size() - 1;
        int leftMax = 0, rightMax = 0;
        while (left < right) {
            leftMax = max(leftMax, height[left]);
            rightMax = max(rightMax, height[right]);
            if (leftMax < rightMax) {
                result += leftMax - height[left];
                left++;
            } else {
                result += rightMax - height[right];
                right--;
            }
        }
        return result;
    }
};

链表

构建虚拟头节点

        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head; 

 移除链表元素

用一个cur遍历,遇到cur->next->val符合条件就跨过next建立节点,最后要返回虚拟头节点的下点

设计链表

struct LinkedNode;MyLinkedList

翻转链表:双指针

while循环cur,cur(head,准备接到pre,变成temp)、pre(Null,准备变成cur)、temp(原cur的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) {}
 * };
 */
class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* left = dummy;
        ListNode* right = dummy;
        if (head == NULL)
            return head;
        // 先计算链表长度
        int n = 0;
        for (ListNode* cur = head; cur != NULL; cur = cur->next)
            n++;
        k %= n;
        while (k-- > 0 && right != NULL)
            right = right->next;
        if (right == NULL)
            return dummy->next;
        // 相当于把后k个元素翻转到前面
        while (right->next != NULL) {
            left = left->next;
            right = right->next;
        }
        right->next = dummy->next;
        dummy->next = left->next;
        left->next = NULL;
        return dummy->next;
    }
};

两两交换链表中的节点

要预留tmp、tmp1,while, cur每次移动两next

 删除链表的倒数第N个节点:双指针

快指针先走n+1步作为与慢指针之间的距离,然后同时移动直到快指针为空则慢指针就到目标位置的前一位,然后跨到下下位

链表相交:双指针

链表长度作差,用差值先把curA移动,再开始一起移动并比较,遇到相等就是要返回的点

ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0;
        //求长度
        while (curA != NULL) {
            lenA++;
            curA = curA->next;
        }
        while (curB != NULL) {
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        if (lenB > lenA) {
            swap (curA,curB);
            swap (lenA,lenB);
        }
        //求长度差
        int gap = lenA - lenB;
        //到同一起点
        while (gap--) {
            curA = curA->next;
        }
        //遍历 curA==curB 不止是值相等 地址也相等 因为是指针
        while (curA != NULL) {
            if (curA == curB){
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;

环形链表II:双指针

快慢指针,快指针一次走两步,慢指针一次走一步,肯定会在环内相遇(入环后相对速度追赶),记录相遇位置作为快指针点,然后慢指针返回起始点,同时一步移动相遇点就是入环点。

哈希表

LRU缓存

核心思路:

  1. 使用哈希表和双向链表组合设计
    • 哈希表:O(1)时间查找节点
    • 双向链表:维护使用顺序
  2. 主要操作流程:
    • 初始化:设置容量,创建虚拟头尾节点
    • get操作:查找键,若存在则移至链表头部并返回值
    • put操作:若键不存在则创建节点放入头部,若缓存满则删除尾部;若键存在则更新值并移至头部
  3. 辅助方法

    addToHead:将节点添加到链表头部removeNode:从链表中移除指定节点moveToHead:将节点移到链表头部removeTail:移除并返回链表尾部节点
struct DLinkedNode {
    int key, value;             // 存储键值对
    DLinkedNode* prev;          // 指向前一个节点的指针
    DLinkedNode* next;          // 指向后一个节点的指针
    DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}  // 默认构造函数
    DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}  // 带参数的构造函数
};

class LRUCache {
private:
    unordered_map<int, DLinkedNode*> cache;  // 哈希表,用于O(1)时间复杂度查找节点
    DLinkedNode* head;                       // 双向链表的虚拟头节点
    DLinkedNode* tail;                       // 双向链表的虚拟尾节点
    int size;                                // 当前缓存中元素个数
    int capacity;                            // 缓存的最大容量

public:
    // 构造函数:初始化缓存容量和双向链表
    LRUCache(int _capacity): capacity(_capacity), size(0) {
        // 使用伪头部和伪尾部节点,简化边界情况处理
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head->next = tail;
        tail->prev = head;
    }
    
    // 获取键对应的值,如果不存在返回-1
    int get(int key) {
        if (!cache.count(key)) {
            return -1;  // 键不存在,返回-1
        }
        // 如果键存在,先通过哈希表定位节点
        DLinkedNode* node = cache[key];
        moveToHead(node);  // 将节点移到链表头部(表示最近使用)
        return node->value;  // 返回值
    }
    
    // 插入或更新键值对
    void put(int key, int value) {
        if (!cache.count(key)) {
            // 键不存在,创建新节点
            DLinkedNode* node = new DLinkedNode(key, value);
            cache[key] = node;        // 添加到哈希表
            addToHead(node);          // 添加到链表头部
            ++size;                   // 缓存大小增加
            if (size > capacity) {
                // 超过容量,删除最久未使用的节点(链表尾部)
                DLinkedNode* removed = removeTail();
                cache.erase(removed->key);  // 从哈希表中删除
                delete removed;             // 释放内存
                --size;                     // 缓存大小减少
            }
        }
        else {
            // 键已存在,更新值并移到链表头部
            DLinkedNode* node = cache[key];
            node->value = value;
            moveToHead(node);  // 表示最近使用
        }
    }

    // 将节点添加到链表头部(表示最近使用)
    void addToHead(DLinkedNode* node) {
        node->prev = head;
        node->next = head->next;
        head->next->prev = node;
        head->next = node;
    }
    
    // 从链表中移除指定节点(不释放内存)
    void removeNode(DLinkedNode* node) {
        node->prev->next = node->next;
        node->next->prev = node->prev;
    }

    // 将节点移到链表头部,表示最近使用
    void moveToHead(DLinkedNode* node) {
        removeNode(node);    // 先从当前位置移除
        addToHead(node);     // 再添加到链表头部
    }

    // 移除并返回链表尾部节点(最久未使用的节点)
    DLinkedNode* removeTail() {
        DLinkedNode* node = tail->prev;  // 真正的尾部节点是tail的前一个
        removeNode(node);                // 从链表中移除
        return node;                     // 返回节点供调用者处理
    }
};

有效的字母异位词:哈希法数组

用数组,记录字符串1中字母出现频率,再减去字符串2中的频率,如果全为0就是异位词

int record[26] = {0};
...
    record[s[i] - 'a']++;

字母异位词分组:哈希法map

        unordered_map<string, vector<string>> mp;
        //把每个词排序前后添加到哈希表,后->前(key->value),key相同的会追加
        //mp["aet"] = ["eat", "tea"]
        for (string& str : strs) {
            string  key = str;
            sort(key.begin(), key.end());
            mp[key].push_back(str);
        }
        vector<vector<string>> ans;
        //把value添加到结果中
        for (auto it = mp.begin(); it != mp.end(); it++) {
            ans.push_back(it->second);
        }
        return ans;

两个数组的交集:哈希法set

用unordered_set<int> 存放结果,再定义一个存放值数组,然后遍历第一个数组给存放值数组对应位置赋值为1,再遍历另一个数组如果对应位置为1就insert到结果集

快乐数:哈希法set

判断是否循环:判断是否出现过,哈希法,先定义一个求各位数上平方和的函数。主函数定义一个unordered_set<int>,while(1) 求和、判断是否为1、是否出现过、没出现过就存进去,直到出现过或者和为1;

if (set.find(sum) != set.end()) {
    return false;
}

两数之和:哈希法map

<key, value>

遍历数组,用目标值减去当前值找有没有在数组中

unordered_map<int,int>  //<key, value>
for..
    if(map.find(target - nums[i]) != map.end()) {
       return {map[target - nums[i]], i};
    }
    // 将当前元素和下标加入到map中
    map[nums[i]] = i;

四数相加II:哈希法map

两数之和升级版,两两看成一对两个for循环就变成了两数之和。

for for
umap[a + b]++;、
...
for for
if (umap.find(0 - (c + d)) != umap.end()){
    count += umap[0 - (c + d)]
}

赎金信:哈希表数组

遍历第一个数组记录,遍历第二个数组减,再遍历看有负值就错,否则就对

record[magazine[i]-'a'] ++;
...
record[ransomNote[j]-'a']--;
if(record[ransomNote[j]-'a'] < 0) {
    return false;
}

三数之和:双指针

ACM中实现sort的话#include <algorithm>

比哈希法简单,先sort排序,其实是三指针了,遍历i剪枝去重,左指针从i+1开始,右指针最右边,while(right>left) 大了右指针左移小了左指针右移,找到了push_back三个下标并while去重

四数之和:双指针

排序后,三数之和上再一层for循环从k开始剪枝去重,for循环i从k+1开始剪枝去重

最长连续序列

字符串

反转字符串:双指针

输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]

一左一右边往中间走边swap换位

反转字符串II

输入: s = "abcdefg", k = 2
输出: "bacdfeg"

for循环以2k递增,有前k个reverse k个,没前k个reverse全部

 for (int i = 0; i < s.size(); i += (2 * k)) {
    if (i + k <= s.size()) {
         reverse(s.begin() + i, s.begin() + i + k );
    } else {
        reverse(s.begin() + i, s.end())

替换数字:双指针

多扩充数字个数乘以number的大小,扩充后一左一右双指针,while左指针大等0,不是数字的直接填过去,是数字右边一直倒着填。

翻转字符串里的单词:双指针

输入: "the sky is blue"
输出: "blue is sky the"

先双指针(start和循环的 i )去除多余空格:遇到非空格再处理,处理之前对slow!=0的s[slow++]=' '补一个空格再开始while(遇到空格停止,表示结束当前单词)循环把非空格的都赋值过去,再reverse整段,再reverse单词 start 和空格位置的i - 1,左闭右闭。

 reverse(s, start, i - 1);

右旋字符串

反转三次就行了

    reverse(s.begin(), s.begin() + len - n);//左
    reverse(s.begin() + len - n, s.end());//右
    reverse(s.begin(), s.end()); //整段

实现 strStr():KMP

 太难了不想看

class Solution {
public:
    void getNext(int* next, const string& s) {
        int j = -1;
        next[0] = j;
        for(int i = 1; i < s.size(); i++) { // 注意i从1开始
            while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
                j = next[j]; // 向前回退
            }
            if (s[i] == s[j + 1]) { // 找到相同的前后缀
                j++;
            }
            next[i] = j; // 将j(前缀的长度)赋给next[i]
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
		vector<int> next(needle.size());
		getNext(&next[0], needle);
        int j = -1; // // 因为next数组里记录的起始位置为-1
        for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始
            while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
                j = next[j]; // j 寻找之前匹配的位置
            }
            if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
                j++; // i的增加在for循环里
            }
            if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串t
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};

重复的子字符串:KMP

class Solution {
public:
    void getNext (int* next, const string& s){
        next[0] = -1;
        int j = -1;
        for(int i = 1;i < s.size(); i++){
            while(j >= 0 && s[i] != s[j + 1]) {
                j = next[j];
            }
            if(s[i] == s[j + 1]) {
                j++;
            }
            next[i] = j;
        }
    }
    bool repeatedSubstringPattern (string s) {
        if (s.size() == 0) {
            return false;
        }
        int next[s.size()];
        getNext(next, s);
        int len = s.size();
        if (next[len - 1] != -1 && len % (len - (next[len - 1] + 1)) == 0) {
            return true;
        }
        return false;
    }
};

栈与队列

各自的语法

栈:push(x) -- 元素 x 入栈

  • pop() -- 移除栈顶元素
  • top() -- 获取栈顶元素
  • empty() -- 返回栈是否为空

队列:push(x) -- 将一个元素放入队列的尾部。
pop() -- 从队列首部移除元素。
peek() -- 返回队列首部的元素。
empty() -- 返回队列是否为空。

用栈实现队列

两个栈,一入一出,在pop的时候,输出栈如果为空,就把进栈数据全部导入进来,如果进栈和出栈都为空的话,说明模拟的队列为空。

用队列实现栈

两个队列,一个用来备份,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。

有效的括号:栈

遇到左括号存对应右括号,遇到右括号和栈顶比较,相等弹出,最后for循环结束且栈空说明有效

删除字符串中的所有相邻重复项:栈

存到栈,栈顶遇到相等的弹出,最后留下来的记得reverse再弹出

逆波兰表达式求值:栈

数字入栈,遇到运算符弹出栈顶两个,注意先后顺序,最后留下来的数字就是答案

滑动窗口求最大值:队列

自定义双端队列

class Solution {
private:
    class MyQueue { //单调队列(从大到小)
    public:
        deque<int> que; // 使用deque来实现单调队列
        // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
        // 同时pop之前判断队列当前是否为空。
        void pop(int value) {
            if (!que.empty() && value == que.front()) {
                que.pop_front();
            }
        }
        // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。
        void push(int value) {
            while (!que.empty() && value > que.back()) {
                que.pop_back();
            }
            que.push_back(value);

        }
        // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
        int front() {
            return que.front();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
            que.push(nums[i]);
        }
        result.push_back(que.front()); // result 记录前k的元素的最大值
        for (int i = k; i < nums.size(); i++) {
            que.pop(nums[i - k]); // 滑动窗口移除最前面元素
            que.push(nums[i]); // 滑动窗口前加入最后面的元素
            result.push_back(que.front()); // 记录对应的最大值
        }
        return result;
    }
};

前 K 个高频元素

小顶堆,每次将最小的元素弹出

class Solution {
public:
    // 小顶堆
    class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 要统计元素出现频率
        unordered_map<int, int> map; // map<nums[i],对应出现的次数>
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 对频率排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        // 用固定大小为k的小顶堆,扫面所有频率的数值
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }

        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;

    }
};

 二叉树

递归三要素

  1. 确定递归函数的参数和返回值

  2. 确定终止条件

  3. 确定单层递归的逻辑

二叉树的递归遍历

就递归 中的时候是push_back进数组

二叉树的迭代遍历:栈迭代

用栈模拟,先放根节点入栈,然后开始while前序就要先中出栈,然后右左入栈

二叉树的层序遍历:队列迭代

层序遍历模板 

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        vector<vector<int>> result;
        while (!que.empty()) {
            int size = que.size();
            vector<int> vec;
            // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.push_back(vec);
        }
        return result;
    }
};

二叉树的最大最小深度

 求深度就是层数->层序遍历模板

最大深度:正常遍历过程中while里面depth++

最小深度:当左右孩子都为空的时候,说明是最低点的一层了,退出

翻转二叉树

迭代中(swap left right)左右

对称二叉树

参数返回值:bool;左右node

终止条件:有空节点(3种)、没有空节点(比较数值)

单层递归逻辑:递归左左、右右;左右、右左;判断这两个同时为true

回溯算法

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

回溯模板

三步:

  • 递归函数的返回值以及参数
  • 回溯函数终止条件
  • 单层搜索的过程
void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

 全排列

记得用一个used数组 回溯前后要true false。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking (vector<int>& nums, vector<bool>& used) {
        // 此时说明找到了一组
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            if (used[i] == true) continue; // path里已经收录的元素,直接跳过
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            path.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

动态规划

五步:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

最大子数组和

dp[i]以i为结尾的最大和, 还要定义一个result

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int> dp(nums.size());
        dp[0] = nums[0];
        int result = dp[0];
        for (int i = 1; i < nums.size(); i++) {
            dp[i] = max(dp[i-1] + nums[i], nums[i]);
            result = max(dp[i], result);
        }
        return result;
    }
};

其他

回文数

使用循环反转数字:

  • 每次取 num 的最后一位(num%10)
  • 将这一位添加到 sum 中(sum=sum*10+num%10)
  • 去掉 num 的最后一位(num/=10)
  • 循环直到 num 变为0
class Solution {
public:
    bool isPalindrome(int x) {
      long i,sum=0,num=x;
      for(i=0;num>0;i++)
      {
        sum=sum*10+num%10;
        num/=10;
      }
      if(sum==x)
      return true;
      else return false;
    }
};

只出现一次的数字

异或:不同为1,相同为0

任何数异或0等于本身:a ^ 0 = a,异或自身等于0。

ACM:

#include <iostream>
#include <vector>
using namespace std;

int singleNumber(vector<int>& nums) {
    int ret = 0;
    for (auto e: nums) {
        ret ^= e;
    }
    return ret;
}

int main() {
    int n;
    cin >> n;  // 读取数组大小
    
    vector<int> nums(n);
    for (int i = 0; i < n; i++) {
        cin >> nums[i];  // 读取数组元素
    }
    
    int result = singleNumber(nums);
    cout << result << endl;  // 输出结果
    
    return 0;
}

环形链表:双指针

class Solution {
public:
    bool hasCycle(ListNode* head) {
        if (head == nullptr || head->next == nullptr) {
            return false;
        }
        ListNode* slow = head;
        ListNode* fast = head->next;
        while (slow != fast) {
            if (fast == nullptr || fast->next == nullptr) {
                return false;
            }
            slow = slow->next;
            fast = fast->next->next;
        }
        return true;
    }
};

最长公共前缀

看成一个行列形式

#include <iostream>
#include <vector>
#include <string>
using namespace std;

string longestCommonPrefix(vector<string>& strs) {
    if (strs.empty()) {
        return "";
    }
    
    string& s0 = strs[0];
    for (int j = 0; j < s0.size(); j++) {
        for (string& s : strs) {
            if (j == s.size() || s[j] != s0[j]) {
                return s0.substr(0, j);
            }
        }
    }
    return s0;
}

int main() {
    int n;
    cin >> n;  // 读取字符串数量
    
    vector<string> strs(n);
    for (int i = 0; i < n; i++) {
        cin >> strs[i];  // 读取每个字符串
    }
    
    string result = longestCommonPrefix(strs);
    cout << result << endl;  // 输出最长公共前缀
    
    return 0;
}

合并两个有序链表:递归

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == nullptr) {
            return l2;
        } else if (l2 == nullptr) {
            return l1;
        } else if (l1->val < l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        } else {
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

删除有序数组中的重复项:双指针

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int slow = 1;
        for (int fast = 1; fast < nums.size(); fast++) {
            if (nums[fast] != nums[fast - 1]) { // nums[i] 不是重复项
                nums[slow++] = nums[fast]; // 保留 nums[i]
            }
        }
        return slow;
    }
};

多数元素

  • 在排序数组中,相同的元素会相邻排列
  • 由于多数元素出现次数大于⌊n/2⌋,所以无论数组长度是奇数还是偶数,数组中间位置一定是多数元素
  • 这是因为即使多数元素恰好占总数的一半以上,排序后它也会占据中间位置
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        return nums[nums.size() / 2];
    }
};

反转字符串中的单词

输入:s = "Let's take LeetCode contest"
输出:"s'teL ekat edoCteeL tsetnoc"
class Solution {
public:
    string reverseWords(string s) 
    {
        int begin1=0;
        int end1=0;
        for(int i=0;i<s.size();i++)
        {
            if(s[i]==' ')
            {
                end1=i;
                reverse(s.begin()+begin1,s.begin()+end1);//reverse左闭右开
                begin1=i+1;//下一次的begin
            }
        }
        //最后一个单词后面没有空格了,所以单独处理
        reverse(s.begin()+begin1,s.end());
        return s; 
    }
};

爬楼梯

class Solution {
public:
    int climbStairs(int n) {
        vector<int> dp(n + 1);
        if(n == 1) return 1;
        if(n == 2) return 2;
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i < n + 1; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

反转链表

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* temp;
        ListNode* pre = NULL;
        ListNode* cur = head;
        while (cur) {
            temp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

合并两个有序数组

算法思路:

  1. 从后向前填充 nums1 数组
  2. 使用三个指针:
    • p1:指向 nums1 有效元素的末尾
    • p2:指向 nums2 有效元素的末尾
    • p:指向 nums1 最终结果的末尾位置

算法步骤:

  1. 初始化三个指针:p1 = m - 1, p2 = n - 1, p = m + n - 1
  2. 当 p2 >= 0 时(即 nums2 还有元素需要合并)循环执行:
    • 比较 nums1[p1] 和 nums2[p2]
    • 将较大者放入 nums1[p] 的位置
    • 移动相应的指针
class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int p1 = m - 1, p2 = n - 1, p = m + n - 1;
        while (p2 >= 0) { // nums2 还有要合并的元素
            // 如果 p1 < 0,那么走 else 分支,把 nums2 合并到 nums1 中
            if (p1 >= 0 && nums1[p1] > nums2[p2]) {
                nums1[p--] = nums1[p1--]; // 填入 nums1[p1]
            } else {
                nums1[p--] = nums2[p2--]; // 填入 nums2[p1]
            }
        }
    }
};

存在重复元素

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_map<int,int> map;
        for (int i = 0; i < nums.size(); i++){
            map[nums[i]]++;
            if (map[nums[i]] >= 2) return true;
        }
        return false;
    }
};

2的幂

执行位运算(如 &, |, ^, ~)时,计算机会直接操作这些数字的二进制表示。

2的幂在二进制中只有一位是1(如4 = 100₂)

对于2的幂,n-1会把那个1变成0,并把右边所有位变成1(如3 = 011₂)

所以对2的幂,n & (n-1)一定等于0

class Solution {
public:
    bool isPowerOfTwo(int n) {
        return n > 0 && (n & (n - 1)) == 0;
    }
};

买股票的最佳时期

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        vector<vector<int>> dp(len, vector<int>(2, 0));
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < len; i++) {
            //第i天持有股票,可能是前一天就持有(dp[i-1][0]),或者是第i天买入(-prices[i])
            dp[i][0] = max(dp[i - 1][0], -prices[i]);
            //第i天不持有股票,可能是前一天就不持有(dp[i-1][1])
            //或者是第i天卖出(prices[i] + dp[i-1][0])
            dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
        }
        return dp[len - 1][1];//最后一天不持有股票的最大利润
    }
};

二叉树的最大深度

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == NULL) return 0;
        int depth = 0;
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty()) {
            int size = que.size();
            depth++; // 记录深度
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
        }
        return depth;
    }
};

手撕加油啊啊啊!!!冲冲冲!!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值