剑指力扣

剑指力扣

文章目录

链表

876 链表的中间结点(简单)

876 链表的中间结点

法一:两次遍历

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:6.4 MB, 在所有 C++ 提交中击败了92.96%的用户

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        int count = 1;
        ListNode* phead = head;
        while ((phead = phead->next) != nullptr) count++;
        phead = head;
        for (int i = 0; i < count / 2; i++) phead = phead->next;
        return phead;
    }
};

法二:快慢指针

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:6.4 MB, 在所有 C++ 提交中击败了86.92%的用户

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* fast = head, * slow = head;
        while (fast != nullptr && 
               fast->next != nullptr) {
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
};

剑指 Offer 06 从尾到头打印链表(简单)

剑指 Offer 06 从尾到头打印链表

执行用时:4 ms, 在所有 C++ 提交中击败了94.51%的用户

内存消耗:8.6 MB, 在所有 C++ 提交中击败了85.66%的用户

法一:辅助栈

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        if (head == nullptr) return {};
        ListNode* phead = head;
        stack<int> s;
        vector<int> res;
        while (phead != nullptr) {
            s.push(phead->val);
            phead = phead->next;
        }
        while (!s.empty()) {
            res.push_back(s.top());
            s.pop();
        }
        return res;
    }
};

法二:递归

class Solution {
public:
    void GetAns(vector<int>& res, ListNode* head) {
        if (head->next != nullptr) GetAns(res, head->next);
        res.push_back(head->val);
    }
    vector<int> reversePrint(ListNode* head) {
        if (head == nullptr) return {};
        vector<int> res;
        GetAns(res, head);
        return res;
    }
};

法三:先反转链表,再输入

执行用时:4 ms, 在所有 C++ 提交中击败了94.51%的用户

内存消耗:8.5 MB, 在所有 C++ 提交中击败了92.68%的用户

参考类似题目:206 反转链表 & 剑指 Offer 24 反转链表

1F55292DD7CF6F2AC88D4840F32B5B69
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> res;
        if (!head) return res;
        //先反转链表
        auto a = head, b = head->next;
        while (b) {
            auto c = b->next;
            b->next = a;
            a = b, b = c;
        }
        head->next = nullptr;
        //反转完毕,a是头结点
        while (a) {
            res.push_back(a->val); 
            a = a->next;
        }
        return res;
    }
};

法四:反转vector容器(思路最简单)

执行用时:8 ms, 在所有 C++ 提交中击败了66.80%的用户

内存消耗:8.4 MB, 在所有 C++ 提交中击败了94.44%的用户

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> res;
        if (head == nullptr) return {};
        while (head != nullptr) {
            res.push_back(head->val);
            head = head->next;
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

141 环形链表(简单)

141 环形链表

法一:快慢指针法:

执行用时:8 ms, 在所有 C++ 提交中击败了93.73%的用户

内存消耗:7.9 MB, 在所有 C++ 提交中击败了29.02%的用户

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

法二:哈希表法:(效率低,但是是一种思路)

执行用时:20 ms, 在所有 C++ 提交中击败了16.61%的用户

内存消耗:9.8 MB, 在所有 C++ 提交中击败了5.13%的用户

class Solution {
public:
    bool hasCycle(ListNode* head) {
        unordered_set<ListNode*> hash;
        while (head != nullptr) {
            if (hash.count(head)) return true;
            hash.insert(head);
            head = head->next;
        }
        return false;
    }
};

24. 两两交换链表中的节点(简单)

24. 两两交换链表中的节点

执行用时:4 ms, 在所有 C++ 提交中击败了75.25%的用户

内存消耗:7.2 MB, 在所有 C++ 提交中击败了97.63%的用户

找准“中间态”即可。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (head == nullptr || head->next == nullptr) return head;
        ListNode* phead = new ListNode, * first = phead, * second = head, * third = head->next;
        phead->next = head;
        while (third != nullptr) {
            first->next = third;
            second->next = third->next;
            third->next = second;
            if (second->next == nullptr || second->next->next == nullptr) break;
            first = second;
            second = second->next;
            third = second->next;
        }
        return phead->next;
    }
};
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if (head == nullptr || head->next == nullptr) return head;
        ListNode* dummy = new ListNode, * p_left = dummy, * p_middle = head, * p_right = head->next;
        while (p_right != nullptr) {
            p_middle->next = p_right->next;
            p_right->next = p_middle;
            p_left->next = p_right;
            p_right = p_middle->next;
            if (p_right == nullptr) break;
            p_right = p_right->next;
            p_left = p_middle;
            p_middle = p_middle->next;
        }
        return dummy->next;
    }
};

2. 两数相加

2. 两数相加

执行用时:56 ms, 在所有 C++ 提交中击败了43.45%的用户

内存消耗:69.4 MB, 在所有 C++ 提交中击败了88.33%的用户

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* p1 = l1, * p2 = l2;
        int len1 = 0, len2 = 0;
        while (p1 != nullptr) { p1 = p1->next; len1++; }
        while (p2 != nullptr) { p2 = p2->next; len2++; }
        if (len1 < len2) return addTwoNumbers(l2, l1);
        p1 = l1, p2 = l2;
        while (p2 != nullptr) {
            p1->val += p2->val;
            p1 = p1->next, p2 = p2->next;
        }
        p1 = l1;
        while (p1->next != nullptr) {
            if (p1->val > 9) {p1->val -= 10; p1->next->val++; }
            p1 = p1->next;
        }
        if (p1->val > 9) {
            ListNode* new_node = new ListNode;
            new_node->next = nullptr;
            new_node->val = 1;
            p1->next = new_node;
            p1->val -= 10;
        }
        return l1;
    }
};

19. 删除链表的倒数第 N 个结点

19. 删除链表的倒数第 N 个结点

执行用时:8 ms, 在所有 C++ 提交中击败了54.91%的用户

内存消耗:10.4 MB, 在所有 C++ 提交中击败了86.38%的用户

关键:如何删除倒数最后一个结点(也就是头结点)?——创建虚拟头结点。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(), * fast = dummy, * slow = fast;
        fast->next = head;
        while (n-- >= 0) fast = fast->next;
        while (fast) fast = fast->next, slow = slow->next;
        slow->next = slow->next->next;
        return dummy->next;
    }
};

237. 删除链表中的节点(简单)

237. 删除链表中的节点

执行用时:8 ms, 在所有 C++ 提交中击败了97.87%的用户

内存消耗:7.6 MB, 在所有 C++ 提交中击败了76.12%的用户

脑筋急转弯!

class Solution {
public:
    void deleteNode(ListNode* node) {
        node->val = node->next->val;
        node->next = node->next->next;
    }
};

61. 旋转链表(中等)

61. 旋转链表

数组

剑指 Offer 03. 数组中重复的数字(简单)

剑指 Offer 03. 数组中重复的数字

法一:哈希表

执行用时:132 ms, 在所有 C++ 提交中击败了30.13%的用户

内存消耗:27.3 MB, 在所有 C++ 提交中击败了20.05%的用户

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_set<int> table;
        for (int i = 0; i < nums.size(); i++) {
             if (table.count(nums[i]) > 0) return nums[i];
            table.insert(nums[i]);
            //思考:为什么下面不行?
            // table.insert(nums[i]);
            // if (table.count(nums[i]) > 1) return nums[i]; 
        }
        return 0;
    }
};

法二:原地置换

无~

剑指 Offer 45. 把数组排成最小的数(中等)

剑指 Offer 45. 把数组排成最小的数

执行用时:12 ms, 在所有 C++ 提交中击败了72.75%的用户

内存消耗:11.7 MB, 在所有 C++ 提交中击败了14.89%的用户

class Solution {
public:
    static bool str_cmp(const string& str1, const string& str2) {
        return str1 + str2 < str2 + str1 ? true : false;
    }
    string minNumber(vector<int>& nums) {
        int n = nums.size();
        vector<string> str;
        string res = "";
        if (n == 0) return res;
        for (int i = 0; i < n; i++) str.push_back(to_string(nums[i]));
        sort(str.begin(), str.end(), str_cmp);
        for (int j = 0; j < str.size(); j++) res += str[j];
        return res;
    }
};

难点:

1.定义排序规则:若字符串 x + y > y + x。则定义 x > y。

2.定义排序函数

剑指 Offer 56 - II. 数组中数字出现的次数 II(中等)

剑指 Offer 56 - II. 数组中数字出现的次数 II

法一:哈希表

执行用时:76 ms, 在所有 C++ 提交中击败了74.05%的用户

内存消耗:18.4 MB, 在所有 C++ 提交中击败了24.49%的用户

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int, int> table;
        for (int i = 0; i < nums.size(); i++) table[nums[i]]++;
        for (unordered_map<int, int>::iterator it = table.begin(); it != table.end(); it++) 
            if (it->second == 1) 
                return it->first;
        /*用下标访问也可:
        for (int i = 0; i < nums.size(); i++) 
            if (table[nums[i]] == 1) 
                return nums[i];
        */
        return -1;
    }
};

法二:二进制各位数字出现的次数

执行用时:80 ms, 在所有 C++ 提交中击败了68.87%的用户

内存消耗:16.4 MB, 在所有 C++ 提交中击败了33.36%的用户

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = 0, arr[32] = { 0 };  //记录各个位上1的个数
        for (int i = 0; i < nums.size(); i++) {
            for (int j = 0; j < 32; j++) {
                if (nums[i] % 2 != 0) arr[j]++;
                nums[i] /= 2;
            }
        }
        for (int k = 0; k < 32; k++) 
            if (arr[k] % 3 != 0) 
                res += pow(2, k);
        return res;
    }
};

46. 全排列(中等)

46. 全排列

执行用时:4 ms, 在所有 C++ 提交中击败了92.27%的用户

内存消耗:7.8 MB, 在所有 C++ 提交中击败了86.53%的用户

class Solution {
public:
    int Num(int num) {
        int res = 1;
        for (int i = 1; i <= num; i++) res *= i;
        return res;
    }

    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int> > res;
        for (int i = 0; i < Num(nums.size()); i++) {
            res.push_back(nums);
            next_permutation(nums.begin(), nums.end());
        }
        return res;
    }
};

使用algorithm中的next_permutation函数会使这道题变得很无脑。以后有机会尝试用其他方法解决。

不要过度依赖algorithm库!

239. 滑动窗口最大值(困难)

239. 滑动窗口最大值

滑动窗口法:(超时)

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        for (int i = 0; i < nums.size() - k + 1; i++) {
            int max = INT_MIN;
            for (int j = 0; j < k; i++) {
                if (nums[j] > max) max = nums[j];
            }
            res.push_back(max);
        }
        return res;
    }
};

字符串

387. 字符串中的第一个唯一字符(简单)

387. 字符串中的第一个唯一字符

执行用时:28 ms, 在所有 C++ 提交中击败了80.91%的用户

内存消耗:10.6 MB, 在所有 C++ 提交中击败了93.55%的用户

class Solution {
public:
    int firstUniqChar(string s) {
        int table[26] = { 0 };
        for (int i = 0; i < s.size(); i++) 
            table[s[i] - 'a']++;
        for (int j = 0; j < s.size(); j++) 
            if (table[s[j] - 'a'] == 1) 
                return j;
        return -1;
    }
};

567. 字符串的排列(中等)

567. 字符串的排列

执行用时:368 ms, 在所有 C++ 提交中击败了5.02%的用户

内存消耗:7.4 MB, 在所有 C++ 提交中击败了79.27%的用户

滑动窗口+哈希表

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        int n1 = s1.size(), n2 = s2.size(), table[26] = { 0 };
        for (int i = 0; i < n1; i++) table[s1[i] - 'a']++;
        for (int j = 0; j < n2; j++) {
            int tmp[26];  //创建table的复制版
            for (int k = 0; k < 26; k++) tmp[k] = table[k];
            int count = 0, index_s2 = j;
            while (index_s2 < n2 && tmp[s2[index_s2++] - 'a']-- > 0) count++;
            if (count == n1) return true;
        }
        return false;
    }
};

14.最长公共前缀(简单)

14. 最长公共前缀

执行用时:4 ms, 在所有 C++ 提交中击败了92.77%的用户

内存消耗:9.5 MB, 在所有 C++ 提交中击败了33.07%的用户

纵向扫描法

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if (strs.size() == 0) return "";
        string res = "";
        for (int j = 0; ; j++) {  //i代表行数,j代表列数
            for (int i = 0; i < strs.size(); i++) {
               char c = strs[0][j];
               if (c != strs[i][j] || j == strs[i].size()) return res;
            }
            res += strs[0][j];
        }
        return res;
    }
};

可以尝试横向扫描法。

38. 外观数列(简单)

38. 外观数列

执行用时:8 ms, 在所有 C++ 提交中击败了70.79%的用户

内存消耗:6.5 MB, 在所有 C++ 提交中击败了88.91%的用户

这道题作为专门的套路,值得背下来!

class Solution {
public:
   string countAndSay(int n) {
       string s = "1";
       for (int i = 0; i < n - 1; i++) {
           string tmp;
           for (int j = 0; j < s.size(); ) {
               int k = j;
               while (j < s.size() && s[k] == s[j]) j++;
               tmp += to_string(j - k) + s[k];
           }
           s = tmp;
       }
       return s;
   }
};

49. 字母异位词分组(中等)

49. 字母异位词分组

法一:哈希表法

执行用时:28 ms, 在所有 C++ 提交中击败了99.77%的用户

内存消耗:19.1 MB, 在所有 C++ 提交中击败了77.88%的用户

记住套路,更关键的是搞清哈希表的对应关系!

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        vector<vector<string> > res;
        unordered_map<string, vector<string> > hash;
        for (auto& value : strs) {
            string key = value;
            sort(key.begin(), key.end());
            hash[key].push_back(value);
        }
        for (auto& item : hash) res.push_back(item.second);
        return res;
    }
};

151. 翻转字符串里的单词(中等)

151. 翻转字符串里的单词

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:6.9 MB, 在所有 C++ 提交中击败了88.18%的用户

难点一:先反转每一个单词,再反转整个字符串。

难点二:去除单词之间多余的空格

class Solution {
public:
    string reverseWords(string s) {
        int left  = 0, right = 0, index = 0;
        while (right < s.size()) {
            while (right < s.size() && s[right] == ' ') right++;
            if (right == s.size()) break;
            left = right;
            while (right < s.size() && s[right] != ' ') right++;
            reverse(s.begin() + left, s.begin() + right);
            if (index) s[index++] = ' ';
            //难点二:
            while (left < right) s[index++] = s[left++];
        }
        s.erase(s.begin() + index, s.end());
        reverse(s.begin(), s.end());
        return s;
    }
};

165. 比较版本号

165. 比较版本号

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:6 MB, 在所有 C++ 提交中击败了97.27%的用户

这道题的很多函数都很重要!需要背下来!

class Solution {
public:
    int compareVersion(string s1, string s2) {
        int p1 = 0, p2 = 0;
        while (p1 < s1.size() || p2 < s2.size()) {
            int x = p1, y = p2;
            while (p1 < s1.size() && s1[p1] != '.') p1++;
            while (p2 < s2.size() && s2[p2] != '.') p2++;
            int a = x == p1 ? 0 : atoi(s1.substr(x, p1 - x).c_str());
            int b = y == p2 ? 0 : atoi(s2.substr(y, p2 - y).c_str());
            if (a > b) return 1;
            else if (a < b) return -1;
            p1 += 1, p2 += 1;
        }
        return 0;
    }
};

929. 独特的电子邮件地址(简单)

929. 独特的电子邮件地址

思路一:

执行用时:24 ms, 在所有 C++ 提交中击败了96.21%的用户

内存消耗:12.9 MB, 在所有 C++ 提交中击败了88.49%的用户

class Solution {
public:
    int numUniqueEmails(vector<string>& emails) {
        unordered_set<string> hash;
        for (auto& letter : emails) {
            int index = 0;
            string tmp;
            for (; letter[index] != '@'; index++) {
                if (letter[index] == '.') continue;
                if (letter[index] == '+') {
                    while (letter[index] != '@') index++;
                    break;
                }
                tmp += letter[index];
            }
            for (; index < letter.size(); index++)
                tmp += letter[index];
            hash.insert(tmp);
        }
        return hash.size();
    }
};

思路二:

执行用时:40 ms, 在所有 C++ 提交中击败了51.49%的用户

内存消耗:13.5 MB, 在所有 C++ 提交中击败了66.03%的用户

熟悉substr()函数的用法

class Solution {
public:
    int numUniqueEmails(vector<string>& emails) {
        unordered_set<string> hash;
        for (auto& email : emails) {
            string name;
            int at = email.find('@');
            for (auto c : email.substr(0, at))
                if (c == '+') break;
                else if (c != '.') name += c;
            name += email.substr(at);
            hash.insert(name);
        }
        return hash.size();
    }
};

5. 最长回文子串

5. 最长回文子串

方法一:中心点扩散法

执行用时:48 ms, 在所有 C++ 提交中击败了73.05%的用户

内存消耗:23.9 MB, 在所有 C++ 提交中击败了56.25%的用户

class Solution {
public:
    string longestPalindrome(string s) {
        string res;
        for (int i = 0; i < s.size(); i++) {
            for (int left = i, right = i; left >= 0 && right < s.size() && s[left] == s[right]; left--, right++)
                if (res.size() < right - left + 1) res = s.substr(left, right - left + 1);
            for (int left = i, right = i + 1; left >= 0 && right < s.size() && s[left] == s[right]; left--, right++)
                if (res.size() < right - left + 1) res = s.substr(left, right - left + 1);
        }
        return res;
    }
};

6. Z 字形变换(中等)

6. Z 字形变换

执行用时:4 ms, 在所有 C++ 提交中击败了98.99%的用户

内存消耗:8.2 MB, 在所有 C++ 提交中击败了89.53%的用户

class Solution {
public:
    string convert(string s, int n) {
        if (n == 1) return s;
        string res;
        for (int i = 0; i < n; i++) {
            if (i == 0 || i == n - 1) {
                for (int j = i; j < s.size(); 
                     j += 2 * (n - 1)) 
                    res += s[j];
                continue;
            }
            for (int k1 = i, k2 = 2 * (n - 1) - i; 
                 k1 < s.size() || k2 < s.size(); 
                 k1 += 2 * (n - 1), k2 += 2 * (n - 1)) {
                if (k1 < s.size()) res += s[k1];
                if (k2 < s.size()) res += s[k2];
            }
        }
        return res;
    }
};

3. 无重复字符的最长子串(中等)

3. 无重复字符的最长子串

法一:哈希表法

思路一:

执行用时:32 ms, 在所有 C++ 提交中击败了63.44%的用户

内存消耗:10.7 MB, 在所有 C++ 提交中击败了36.29%的用户

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int res = 0;
        unordered_set<char> hash;
        for (int left = 0, right = 0; right < s.size(); right++) {
            while (hash.count(s[right])) {
                hash.erase(s[left]);
                left++;
            }
            hash.insert(s[right]);
            res = max(res, right - left + 1);
        }
  return res;
    }
};

思路二:

执行用时:16 ms, 在所有 C++ 提交中击败了75.92%的用户

内存消耗:8.1 MB, 在所有 C++ 提交中击败了67.48%的用户

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int res = 0;
        unordered_map<char, int> hash;
        for (int left = 0, right = 0; right < s.size(); right++) {
            hash[s[right]]++;
            while (hash[s[right]] > 1) hash[s[left++]]--;
            res = max(res, right - left + 1);
        }
        return res;
    }
};

法二:暴力两遍扫描

执行用时:1076 ms, 在所有 C++ 提交中击败了5.00%的用户

内存消耗:238.3 MB, 在所有 C++ 提交中击败了4.97%的用户

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int length = 0;
        for (int i = 0; i < s.size(); i++) {
            unordered_set<char> hash;
            for (int j = i; j < s.size(); j++) {
                if (hash.count(s[j])) {
                    if (length < j - i) length = j - i;
                    break;
                }
                if (j == s.size() - 1)
                    if (length < j - i + 1) length = j - i + 1;
                hash.insert(s[j]);
            }
        }
        return length;
    }
};

208. 实现 Trie (前缀树)(中等)

208. 实现 Trie (前缀树)

执行用时:72 ms, 在所有 C++ 提交中击败了88.26%的用户

内存消耗:43.9 MB, 在所有 C++ 提交中击败了29.46%的用户

class Trie {
public:
    struct Node {
        Node* sons[26];
        bool is_end;
        Node() {
            is_end = false;
            for (int i = 0; i < 26; i++) sons[i] = nullptr;
        }
    }*root;
    /** Initialize your data structure here. */
    Trie() {
        root = new Node();
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        Node* new_node = root;
        for (auto c : word) {
            if (!new_node->sons[c - 'a']) new_node->sons[c - 'a'] = new Node();
            new_node = new_node->sons[c - 'a'];
        }
        new_node->is_end = true;
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        Node* find = root;
        for (auto c : word) {
            if (!find->sons[c - 'a']) return false;
            find = find->sons[c - 'a'];
        }
        return find->is_end;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        Node* find = root;
        for (auto c : prefix) {
            if (!find->sons[c - 'a']) return false;
            find = find->sons[c - 'a'];
        }
        return true;
    }
};

273. 整数转换英文表示

273. 整数转换英文表示

贪心

455. 分发饼干(简单)

455. 分发饼干

执行用时:32 ms, 在所有 C++ 提交中击败了99.05%的用户

内存消耗:17.1 MB, 在所有 C++ 提交中击败了98.40%的用户

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int index_s = 0, index_g = 0;
        while (index_s < s.size() && index_g < g.size())
            if (g[index_g] <= s[index_s]) { index_s++; index_g++; }
            else index_s++;
        return index_g;
    }
};

贪心的标志算法

55.跳跃游戏(中等)

55. 跳跃游戏

执行用时:8 ms, 在所有 C++ 提交中击败了99.24%的用户

内存消耗:12.5 MB, 在所有 C++ 提交中击败了94.64%的用户

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

45. 跳跃游戏II

45. 跳跃游戏 II

执行用时:8 ms, 在所有 C++ 提交中击败了99.81%的用户

内存消耗:15.1 MB, 在所有 C++ 提交中击败了93.23%的用户

class Solution {
public:
    int jump(vector<int>& nums) {
        if (nums.size() == 1) return 0;
        int left = 0, right = 0, steps = 0;  //用left和right维护一个区间
        while (left <= right) {
            int max_r = 0;
            for (int i = left; i <= right; i++)
                max_r = max(max_r, nums[i] + i);
            left = right, right = max_r;
            steps++;
            if (right >= (int)nums.size() - 1) break;
        }
        return steps;
    }
};

位运算

231. 2的幂(简单)

231. 2的幂

方法一:

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:5.7 MB, 在所有 C++ 提交中击败了98.60%的用户

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

方法二:

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:5.8 MB, 在所有 C++ 提交中击败了97.10%的用户

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

x & -x表示x的二进制中最靠近右边的1及以后的数

762. 二进制表示中质数个计算置位(简单)

762. 二进制表示中质数个计算置位

执行用时:40 ms, 在所有 C++ 提交中击败了39.26%的用户

内存消耗:6.2 MB, 在所有 C++ 提交中击败了58.97%的用户

class Solution {
public:
    int countPrimeSetBits(int L, int R) {
        unordered_set<int> table = { 2,3,5,7,11,13,17,19,23,29,31 };
        int num = 0;
        for (int i = L; i <= R; i++) {
            int n = 0;
            for (int j = i; j; j >>= 1) n += (j & 1);  //计算二进制1的个数。常见技巧!
            if (table.count(n)) num++;
        }
        return num; 
    }
};

136. 只出现一次的数字

136. 只出现一次的数字

执行用时:12 ms, 在所有 C++ 提交中击败了99.83%的用户

内存消耗:16.5 MB, 在所有 C++ 提交中击败了96.73%的用户

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

技巧: a ^ a = 0 a ^ 0 = a

137. 只出现一次的数字 II(中等)

137. 只出现一次的数字 II

执行用时:8 ms, 在所有 C++ 提交中击败了95.41%的用户

内存消耗:9.1 MB, 在所有 C++ 提交中击败了99.32%的用户

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (int i = 0; i < 32; i++) {
            int j, value = 0;
            for (j = 0; j < nums.size(); j++) 
                value += (nums[j] >> i) & 1;
            if (value % 3) ret += (1 << i);
        }
        return ret;
    }
};

260 只出现一次的数字 III(中等)

260. 只出现一次的数字 III

执行用时:12 ms, 在所有 C++ 提交中击败了92.41%的用户

内存消耗:9.8 MB, 在所有 C++ 提交中击败了93.97%的用户

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int p1 = 0, p2 = 0, i;
        for (auto x : nums) p1 ^= x;
        for (i = 0; i < 32; i++) 
            if ((p1 >> i) & 1) break;  
        for (auto y : nums) if ((y >> i) & 1) p2 ^= y;
        return {p2, p1 ^ p2};
    }
};

哈希

1. 两数之和(简单)

1. 两数之和

执行用时:8 ms, 在所有 C++ 提交中击败了90.24%的用户

内存消耗:9.9 MB, 在所有 C++ 提交中击败了25.03%的用户

心得:使用哈希表时,找准对应关系:nums[i]对应hash的key值,i(下标)对应hash的value值。count函数用于查找key是否出现。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hash;
        for (int i = 0; i < nums.size(); i++) hash[nums[i]] = i;
        for (int j = 0; j < nums.size(); j++) {
            int value = target - nums[j];
            if (hash.count(value) && hash[value] != j) return {j, hash[value]};
        }
        return {};
    }
};

考虑到在填充哈希表的过程中,可以随时检测是否找到目标值,因此出现了以下的优化算法。

执行用时:4 ms, 在所有 C++ 提交中击败了98.88%的用户

内存消耗:8.7 MB, 在所有 C++ 提交中击败了94.20%的用户

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        //key用于记录nums中的值,value记录nums中对应的值的下标
        unordered_map<int, int> hash;
        for (int i = 0; i < nums.size(); i++) {
            //先判断“补数”是否存在,防止“补数”是自己的情况发生
            int value = target - nums[i];
            //count函数用于记录key出现的次数
            if (hash.count(value)) 
                return {i, hash[value]};
            hash[nums[i]] = i;
        }
        return {};
    }
};

注:也可以用两层for暴力求解。

454. 四数相加 II(中等)

454. 四数相加 II

执行用时:316 ms, 在所有 C++ 提交中击败了96.66%的用户

内存消耗:45.4 MB, 在所有 C++ 提交中击败了37.66%的用户

这道题是第1题的升级版,思路几乎一样,唯一的技巧就是将两两数组看成一个整体。

class Solution {
public:
    int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
        int res = 0;
        unordered_map<int, int> hash;
        for (auto& x : A)
            for (auto& y : B)
                hash[x + y]++;
        for (auto& x : C)
            for (auto& y : D)
                res += hash[-(x + y)];
        return res;
    }
};

560. 和为K的子数组(中等)

560. 和为K的子数组

思路:任何一个连续子数组都可以由两个前缀和数组的差表示。

例如:3,4,5号位数之和可以由前5号位之和减去前(3-1)号位之和表示。

执行用时:132 ms, 在所有 C++ 提交中击败了81.37%的用户

内存消耗:40.5 MB, 在所有 C++ 提交中击败了27.67%的用户

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int res = 0, pre = 0;  //pre表示前n项和
        //hash的key值表示前n项和,value表示该key值出现的次数。
        unordered_map<int, int> hash;
        hash[0] = 1;  //为什么要有这个临界条件?
        for (int i = 0; i < nums.size(); i++) {
            pre += nums[i];
            res += hash[pre - k];
            hash[pre]++;
        }
        return res;
    }
};

二分

69. x 的平方根(简单)

69. x 的平方根

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:5.9 MB, 在所有 C++ 提交中击败了91.55%的用户

  1. 一般情况下,mid值会先停留在目标值处。
  2. keypoint1决定了mid值(目标值)的平方一定小于或等于x,满足题意。
  3. 紧接着左边界会停留在mid值处,也就是目标值处。
  4. 让左边界还是右边界先停留在目标值,是要根据第二行决定的。
  5. 最终左右边界会挨在一起。例如:左边界为4,右边界为5。
  6. 为了让left < right。需要让right = mid - 1。
  7. 当挨在一起的时候,mid值不能是目标值。因此需要微调keypoint3语句。
class Solution {
public:
    int mySqrt(int x) {
        int left = 0, right = x;
        while (left < right) {
            int mid = (1ll + left + right) / 2;  //keypoint3
            if (1ll * mid * mid <= x) left = mid;  //keypoint1
            else right = mid - 1;  //keypoint2
        }
        return left;
    }
};

步骤:

  1. 首先写框架:
int mySqrt(int x) {
    int left = 0, right = x;
    while (left < right) {
        int mid = (left + right) / 2;
        if (mid * mid <= x)
    }
}
  1. 然后决定让左边界靠近mid值:
//int mySqrt(int x) {
//    int left = 0, right = x;
//    while (left < right) {
//        int mid = (left + right) / 2;
//        if (mid * mid <= x)
              left = mid;
//    }
//}
  1. 死记硬背下面的else语句:
//int mySqrt(int x) {
//    int left = 0, right = x;
//    while (left < right) {
//        int mid = (left + right) / 2;
//        if (mid * mid <= x) left = mid;
          else right = mid - 1;
//    }
//}
  1. 当左右边界靠在一起的时候,mid值不能是目标值(很玄幻,也很关键):
//int mySqrt(int x) {
//    int left = 0, right = x;
//    while (left < right) {
          int mid = (left + right + 1) / 2;
//        if (mid * mid <= x) left = mid;
//        else right = mid - 1;
//    }
//}
  1. 修改细节,返回答案
int mySqrt(int x) {
    int left = 0, right = x;
    while (left < right) {
        int mid = (1ll + left + right) / 2;
        if (1ll * mid * mid <= x) left = mid;
        else right = mid - 1;
    }
    return left;
}

34. 在排序数组中查找元素的第一个和最后一个位置(中等)

34. 在排序数组中查找元素的第一个和最后一个位置

执行用时:8 ms, 在所有 C++ 提交中击败了98.43%的用户

内存消耗:13.3 MB, 在所有 C++ 提交中击败了94.28%的用户

法一:二分法(很重要!)

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if (nums.empty()) return {-1, -1};
        int left = 0, right = nums.size() - 1;
        vector<int> res;
        while (left < right) {
            int mid = left + right >> 1;  //重要
            if (nums[mid] >= target) right = mid;
            else left = mid + 1;
        }
        if (nums[right] != target) return {-1, -1};
        res.push_back(right);
        left = right, right = nums.size() - 1;
        while (left < right) {
            int mid = left + right + 1 >> 1;  //重要
            if (nums[mid] <= target) left = mid;
            else right = mid - 1;
        }
        res.push_back(left);
        return res;
    }
};

法二:一遍扫描

执行用时:4 ms, 在所有 C++ 提交中击败了99.86%的用户

内存消耗:13.3 MB, 在所有 C++ 提交中击败了94.95%的用户

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int start = -1, end = -1;
        for (int i = 0; i < nums.size(); i++) {
            if (start == -1 && nums[i] == target) 
                start = i, end = i;
            if (start != -1 && nums[i] == target) 
                end = i;
        }
        return {start, end};
    }
};

162. 寻找峰值(中等)

162. 寻找峰值

方法一:二分法:

执行用时:4 ms, 在所有 C++ 提交中击败了96.27%的用户

内存消耗:8.7 MB, 在所有 C++ 提交中击败了81.79%的用户

难点是识别出二分法,而二分法很简单。

 

方法二:一遍扫描:

执行用时:4 ms, 在所有 C++ 提交中击败了95.43%的用户

内存消耗:8.6 MB, 在所有 C++ 提交中击败了83.42%的用户

int findPeakElement(vector<int>& nums) {
        int res = nums[0] > nums[nums.size() - 1] ? 0 : nums.size() - 1;
        for (int i = 1; i < nums.size() - 1; i++) {
            if (nums[i] > nums[i - 1] && nums[i] > nums[i + 1]) {
                res = i;
                break;
            }
        }
        return res;
    }

74. 搜索二维矩阵(中等)

74. 搜索二维矩阵

执行用时:8 ms, 在所有 C++ 提交中击败了66.30%的用户

内存消耗:9.3 MB, 在所有 C++ 提交中击败了81.93%的用户

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size(), n = matrix[0].size();
        if (m == 0 || n == 0) return false;
        int left = 0, right = m * n - 1;
        while (left < right) {
            int mid = left + right >> 1;
            if (matrix[mid / n][mid % n] >= target) 
                right = mid;  //有点绕
            else left = mid + 1;
        }
        return matrix[right / n][right % n] == target;
    }
};

240. 搜索二维矩阵 II(中等)

240. 搜索二维矩阵 II

执行用时:116 ms, 在所有 C++ 提交中击败了91.97%的用户

内存消耗:14.5 MB, 在所有 C++ 提交中击败了58.02%的用户

这道题和二分法没啥关系,是一个固定的套路题

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if (matrix.empty() || matrix[0].empty()) return false;
        int i = 0, j = matrix[0].size() - 1;
        while (i < matrix.size() && j >= 0) {
            if (target == matrix[i][j]) return true;
            else if (target < matrix[i][j]) j--;
            else i++;
        }
        return false;
    }
};

155. 最小栈

155. 最小栈

执行用时:20 ms, 在所有 C++ 提交中击败了98.78%的用户

内存消耗:15.5 MB, 在所有 C++ 提交中击败了15.16%的用户

单调栈:套路

难点是在常数时间内找出最小值。

class MinStack {
    stack<int> stk;
    stack<int> min;
public:
    MinStack() {
       
    }
    
    void push(int x) {
        stk.push(x);
        if (min.empty() || x <= min.top()) min.push(x);
    }
    
    void pop() {
        if (stk.top() == min.top()) min.pop();
        stk.pop();
    }
    
    int top() {
       return stk.top();
    }
    
    int getMin() {
        return min.top();
    }
};

496. 下一个更大元素 I(简单)

496. 下一个更大元素 I

法一:单调栈

执行用时:4 ms, 在所有 C++ 提交中击败了98.73%的用户

内存消耗:8.7 MB, 在所有 C++ 提交中击败了70.46%的用户

都是套路,全靠记住

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        unordered_map<int, int> hash;
        stack<int> stk;
        vector<int> res;
        for (int i = nums2.size() - 1; i >= 0; i--) {
            //先弹栈
            while (stk.size() && nums2[i] > stk.top()) 
                stk.pop();
            hash[nums2[i]] = stk.size() ? stk.top() : -1;
            //再压栈
            stk.push(nums2[i]);
        }
        for (auto x : nums1) res.push_back(hash[x]);
        return res;
    }
};

法二:暴力求解

执行用时:20 ms, 在所有 C++ 提交中击败了25.61%的用户

内存消耗:8.1 MB, 在所有 C++ 提交中击败了98.77%的用户

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        vector<int> res;
        for (int i = 0; i < nums1.size(); i++) {
            int j = 0;
            while (nums2[j] != nums1[i]) j++;
            int tmp = nums2[j];
            while (j < nums2.size()) 
                if (nums2[j] > tmp) {
                    res.push_back(nums2[j]);
                    break;
                }
                else j++;
            if (j == nums2.size()) res.push_back(-1);
        }
        return res;
    }
};

DFS和BFS

199. 二叉树的右视图

199. 二叉树的右视图

执行用时:4 ms, 在所有 C++ 提交中击败了79.76%的用户

内存消耗:11.5 MB, 在所有 C++ 提交中击败了83.17%的用户

显然要一层一层地搜索,考虑使用宽搜(BFS)。并且借助队列。

推荐视频:宽度搜索

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int> res;
        if (!root) return res;
        queue<pair<TreeNode*, int>> q;
        q.push(make_pair(root, 0));
        while (q.size()) {
            TreeNode* node = q.front().first;
            int level = q.front().second;
            q.pop();
            if (res.size() <= level) res.push_back(0);
            res[level] = node->val;
            if (node->left) q.push(make_pair(node->left, level + 1));
            if (node->right) q.push(make_pair(node->right, level + 1));
        }
        return res;
    }
};

236. 二叉树的最近公共祖先

236. 二叉树的最近公共祖先

执行用时:12 ms, 在所有 C++ 提交中击败了98.91%的用户

内存消耗:16.8 MB, 在所有 C++ 提交中击败了16.58%的用户

心得:

  1. 通过深搜,搜出两个节点的路径。例如:路径一(3,1,2,4),路径二(3,1,5)。则最近公共祖先为1。

  2. 使用深搜,则一定需要递归。递归的结束条件有两个:一是遇到目标,二是遇到空值。多看几次代码,熟悉这个模型。

  3. 脑袋中要有深搜的一个动态图。

推荐视频:深度搜索

class Solution {
public:
    void dfs_search(vector<TreeNode*>& stk, vector<TreeNode*>& path, TreeNode* target, TreeNode* node) {
        if (!node) return;
        stk.push_back(node);
        if (target == node) {
            path = stk;
            return;
        } 
        dfs_search(stk, path, target, node->left);  //一定会先把最左边的路走完
        dfs_search(stk, path, target, node->right);  //然后依次从远离根结点的右边的结点向根结点靠近
        stk.pop_back();  //当一个结点的左右子结点都扫描完后,应该弹出该结点。
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*> stk, p_path, q_path;
        dfs_search(stk, p_path, p, root);
        stk.clear();
        dfs_search(stk, q_path, q, root);
        int common = 0;
        while (common < p_path.size() && common < q_path.size() && p_path[common] == q_path[common]) common++;
        return p_path[--common];
    }
};

279. 完全平方数(中等)

279. 完全平方数

执行用时:96 ms, 在所有 C++ 提交中击败了85.14%的用户

内存消耗:11.8 MB, 在所有 C++ 提交中击败了19.19%的用户

  1. 创建一个列表,列表的下标表示正整数,值表示最少需要多少步到达原点0。
  2. 创建队列,队列的值表示dist中的下标值。
  3. 初始化列表dist,dist[0] = 0,初始化队列,放入下标0。
B611D3C431ACEF1E44465717CB2FEE0A
class Solution {
public:
    int numSquares(int n) {
        queue<int> q;
        vector<int> dist(n + 1, INT_MAX);
        q.push(0), dist[0] = 0;
        while (q.size()) {
            int num = q.front();
            q.pop();
            if (num == n) return dist[num];
            for (int i = 1; i * i + num <= n; i++) {
                int j = i * i + num;
                if (dist[j] > dist[num] + 1) {
                    dist[j] = dist[num] + 1;
                    q.push(j);
                }
            }
        }
        return 0;
    }
};

733. 图像渲染(简单)

733. 图像渲染

执行用时:12 ms, 在所有 C++ 提交中击败了84.09%的用户

内存消耗:13.4 MB, 在所有 C++ 提交中击败了80.65%的用户

步骤一:做一个向四个方向发散的深搜

vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
    int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, 1, 0, -1 };
    for (int i = 0; i < 4; i++) {
        int x = dx[i] + sr, y = dx[i] + sc;
        if (x >= 0 && x < image.size() 
            && y >= 0 && y < image[0].size() 
            && image[x][y] == image[sr][sc]) 
            floodFill(image, x, y, newColor);
}

步骤二:在每一个floodFill函数中修改color值

//vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
//  int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, 1, 0, -1 };
    int oldColor = image[sr][sc];
//  image[sr][sc] = newColor;
//  for (int i = 0; i < 4; i++) {
//    int x = dx[i] + sr, y = dx[i] + sc;
//    if (x >= 0 && x < image.size() 
//		  && y >= 0 && y < image[0].size() 
		  && image[x][y] == oldColor)
//      floodFill(image, x, y, newColor);
//  }
//}

步骤三:判断一些细节步骤:


class Solution {
public:
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
        if (image.empty() || image[0].empty()) return image;
        int dy[4] = { -1, 0, 1, 0 }, dx[4] = { 0, 1, 0, -1 };
        int oldColor = image[sr][sc];
        if (oldColor == newColor) return image;
        image[sr][sc] = newColor;
        for (int i = 0; i < 4; i++) {
            int x = sr + dx[i], y = sc + dy[i];
            if (x >= 0 && x < image.size() && y >= 0 && y < image[0].size() && image[x][y] == oldColor)
                floodFill(image, x, y, newColor);
        }
        return image;
    }
};

200. 岛屿数量(中等)

200. 岛屿数量

执行用时:16 ms, 在所有 C++ 提交中击败了96.26%的用户

内存消耗:9.3 MB, 在所有 C++ 提交中击败了90.60%的用户

这道题和733题很类似。

class Solution {
public:
    void dfs_serach(vector<vector<char>>& grid, int sr, int sc, int& res) {
        int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, 1, 0, -1 };
        grid[sr][sc] = '0';
        for (int k = 0; k < 4; k++) {
            int x = dx[k] + sr, y = dy[k] + sc;
            if (x >= 0 && x < grid.size() &&
                y >= 0 && y < grid[0].size() &&
                grid[x][y] == '1') 
                    dfs_serach(grid, x, y, res);
        }
    }

    int numIslands(vector<vector<char>>& grid) {
        int m = grid.size(), n = grid[0].size(), res = 0;
        if (!m || ! n) return 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '1') {
                    res++;
                    dfs_serach(grid, i, j, res);
                }
            }
        }
        return res;
    }
};

130. 被围绕的区域(中等)

130. 被围绕的区域

784. 字母大小写全排列(中等)

784. 字母大小写全排列

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:9.8 MB, 在所有 C++ 提交中击败了78.00%的用户

class Solution {
public:
    vector<string> letterCasePermutation(string S) {
        vector<string> res;
        dfs(S, res, 0);
        return res;
    }
    void dfs(string S, vector<string>& res, int n) {
        if (n == S.size()) {  //字符串的末尾还有一个'\0'
            res.push_back(S);
            return;
        }
        dfs(S, res, n + 1);
        if (S[n] >= 'A') {
            S[n] ^= ' ';
            dfs(S, res, n + 1);
        }
    }
};

77. 组合(中等)

77. 组合

执行用时:24 ms, 在所有 C++ 提交中击败了69.82%的用户

内存消耗:9.8 MB, 在所有 C++ 提交中击败了64.98%的用户

脑袋里要有一个树状图。

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> combine(int n, int k) {
        dfs(n, k, 1);
        return res;
    }
    void dfs(int n, int k, int start) {
        if (!k) {
            res.push_back(path);
            return;
        }
        for (int i = start; i <= n; i++) {
            path.push_back(i);
            dfs(n, k - 1, i + 1);
            path.pop_back();
        }
    }
};

步骤一:写出深搜的框架

	vector<vector<int>> res;
	vector<vector<int>> combine(int n, int k) {
        dfs();
        return res;
    }
	void dfs() {}

步骤二:将每一次搜索的结果存入一个临时变量path中,并在深搜结束后放入结果res中。

//	vector<vector<int>> res;
	vector<int> path;
//	vector<vector<int>> combine(int n, int k) {
//      dfs();
//      return res;
//  }
	void dfs() {
        if (终止条件) {
            res.push_back(path);
            return;
        }
    }

步骤三:考虑dfs的参数

//	vector<vector<int>> res;
//	vector<int> path;
//	vector<vector<int>> combine(int n, int k) {
        dfs(n, k, 1);  
//      return res;
//  }
	void dfs(int n, int k, int start) {  //n代表每层遍历的终点,k代表还需要选取的字母个数,start表示该层开始遍历的起始点。
        if (k == 0) {
//          res.push_back(path);
//          return;
//      }
//  }

步骤四:填充dfs的内容

//	vector<vector<int>> res;
//	vector<int> path;
//	vector<vector<int>> combine(int n, int k) {
        dfs(n, k, 1);  
//      return res;
//  }
//	void dfs(int n, int k, int start) {  //n代表每层遍历的终点,k代表还需要选取的字母个数,start表示该层开始遍历的起始点。
//      if (k == 0) {
//          res.push_back(path);
//          return;
//      }
		for (int i = start, i <= n; i++) {
            path.push_back(i);
            dfs(n, k - 1, i + 1);
            path.pop_back();  //回溯
        }

//  }

257. 二叉树的所有路径(简单)

257. 二叉树的所有路径

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:12.9 MB, 在所有 C++ 提交中击败了60.14%的用户

对比77题的path和这里的path的区别。

class Solution {
public:
    vector<string> res;
    vector<string> binaryTreePaths(TreeNode* root) {
        string path;
        dfs(root, path);
        return res;
    }
    void dfs(TreeNode* root, string path) {
        if (!root) return;
        if (path.size()) path += "->";
        path += to_string(root->val);
        if (!root->left && !root->right) {
            res.push_back(path);
            return;
        }
        dfs(root->left, path);
        dfs(root->right, path);
    }
};

93. 复原IP地址(中等)

93. 复原IP地址

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:6.7 MB, 在所有 C++ 提交中击败了80.05%的用户

class Solution {
public:
    vector<string> res;
    vector<string> restoreIpAddresses(string s) {
        string path;
        dfs(s, 0, 0, path);
        return res;
    }
    void dfs(string& s, int start, int area, string path) {
        if (area == 4) {
            if (start == s.size()) res.push_back(path.substr(1));
            return;
        }
        if (s[start] == '0') dfs(s, start + 1, area + 1, path + ".0");
        else {
            for (int i = start, k = 0; i < s.size(); i++) {
                k = k * 10 + s[i] - '0';
                if (k < 256) dfs(s, i + 1, area + 1, path + '.' + to_string(k));
                else break;
            }
        }
    }
};

步骤一:使用深搜,确定深搜的初始状态以及终止条件

vector<string> res;
vector<string> restoreIpAddresses(string s) {
    string path;
    dfs(s, 0, 0, path);
    return res;
}
void dfs(string& s, int index, int area, string path) {
    if (area == 4) {
        if (index == s.size()) res.push_back(path.substr(1));
        return;
    } 
}

步骤二:考虑特殊情况,如果该为为0,则这个区域只能有一个0

//vector<string> res;
//vector<string> restoreIpAddresses(string s) {
//  string path;
//  dfs(s, 0, 0, path);
//  return res;
//}
//void dfs(string& s, int index, int area, string path) {
//  if (area == 4) {
//    if (index == s.size()) res.push_back(path.substr(1));
//    return;
//  }
    if (s[index] == '0') dfs(s, index + 1, area + 1, path + ".0");
//}

步骤三:考虑一般情况的深搜。任何小于256的数都可以作为一个合法的area。

//vector<string> res;
//vector<string> restoreIpAddresses(string s) {
//  string path;
//  dfs(s, 0, 0, path);
//  return res;
//}
//void dfs(string& s, int index, int area, string path) {
//  if (area == 4) {
//    if (index == s.size()) res.push_back(path.substr(1));
//    return;
//  }
//  if (s[index] == '0') dfs(s, index + 1, area + 1, path + ".0");
//  else {
      for (int i = index, j = 0; i < s.size(); i++) {
        j = j * 10 + s[i] - '0';
        if (j < 256) dfs(s, i + 1, area + 1, path + '.' + to_string(j));
        else break;
      }
    }
//}

98. 验证二叉搜索树(中等)

98. 验证二叉搜索树

执行用时:16 ms, 在所有 C++ 提交中击败了70.75%的用户

内存消耗:21 MB, 在所有 C++ 提交中击败了72.10%的用户

思路:

  1. 当前结点左边的值都比这个结点的值小,右边的值都比这个结点的值大。
  2. 所有值的范围在INT_MIN~INT_MAX之间。
  3. 写一个二叉树形状的递归。
class Solution {
public:
    bool isValidBST(TreeNode* root) {
        return dfs(root, INT_MIN, INT_MAX);
    }
    bool dfs(TreeNode*root, long long minv, long long maxv) {
        if (!root) return true;
        if (root->val > maxv || root->val < minv) return false;
        return dfs(root->left, minv, root->val - 1ll) && dfs(root->right, root->val + 1ll, maxv);
    }
};

步骤1:做一个深搜,确定深搜的结束条件和参数

bool isValidBST(TreeNode* root) {
    return dfs(root, INT_MIN, INT_MAX);
}
void dfs(TreeNode*root, long long minv, long long maxv) {
    if (!root) return true;
}

94. 二叉树的中序遍历(中等)

94. 二叉树的中序遍历

法一:递归

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:8.1 MB, 在所有 C++ 提交中击败了88.37%的用户

class Solution {
public:
    vector<int> res;
    vector<int> inorderTraversal(TreeNode* root) {
        dfs(root);
        return res;
    }
    void dfs(TreeNode* root) {
        if (!root) return;
        dfs(root->left);
        res.push_back(root->val);
        dfs(root->right);
    }
};

步骤一:使用深搜,确定终止条件

vector<int> res;
vector<int> inorderTraversal(TreeNode* root) {
    dfs(root);
    return res;
}
void dfs(TreeNode* root) {
    if (!root) return;
    dfs(root->left);
}

步骤二:将该结点的值放入res数组中,再切换到该结点的右支,继续深搜

//vector<int> res;
//vector<int> inorderTraversal(TreeNode* root) {
//  dfs(root);
//  return res;
//}
//void dfs(TreeNode* root) {
//  if (!root) return;
//  dfs(root->left);
	res.push_back(root->val);
	dfs(root->right);
//}

法二:迭代

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:8.1 MB, 在所有 C++ 提交中击败了91.95%的用户

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> stk;
        TreeNode* p = root;
        while (p || stk.size()) {
            while (p) {
                stk.push(p);
                p = p->left;
            }
            TreeNode* node = stk.top();
            stk.pop();
            res.push_back(node->val);
            p = node->right;
        }
        return res;
    }
};

步骤一:递归的本质是对数据结构栈的操作,因此需要维护一个栈

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> res;
    stack<TreeNode*> stk;
}

步骤二:用“栈”模拟递归。难点是模拟“深搜”与“回溯”。

//vector<int> inorderTraversal(TreeNode* root) {
//  vector<int> res;
//  stack<TreeNode*> stk;
    while (终止条件) {
      //深搜
      while (root) {
        stk.push(root);
        root = root->left;
      }
      //回溯
      root = stk.top();
      stk.pop();
      //切换分支,继续深搜
      root = root-> right;
//  }
//}

步骤三:判断递归的终止条件

//vector<int> inorderTraversal(TreeNode* root) {
//  vector<int> res;
//  stack<TreeNode*> stk;
    while (终止条件) {
      //深搜
      while (root) {
        stk.push(root);
        root = root->left;
      }
      //回溯
      root = stk.top();
      res.push_back(root->val);
      stk.pop();
      //切换分支,继续深搜
      root = root-> right;
//  }
//}

步骤四:确定终止条件,返回答案

//vector<int> inorderTraversal(TreeNode* root) {
//  vector<int> res;
//  stack<TreeNode*> stk;
    while (root || stk.size()) {
//    while (root) {
//      stk.push(root);
//      root = root->left;
//    }
//    root = stk.top();
//    res.push_back(root->val);
//    stk.pop();
//    root = root->right;
//  }
    return res;
//}

101. 对称二叉树(简单)

101. 对称二叉树

执行用时:4 ms, 在所有 C++ 提交中击败了84.84%的用户

内存消耗:15.9 MB, 在所有 C++ 提交中击败了20.64%的用户

难点:如何深搜

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (!root) return true;
        return dfs(root->left, root->right);
    }
    bool dfs(TreeNode* p, TreeNode* q) {
        if (!p && q || p &&!q) return false;
        if (!p && !q) return true;
        return p->val == q->val && dfs(p->left, q->right) && dfs(p->right, q->left);
    }
};

步骤一:确定深搜初始条件

bool isSymmetric(TreeNode* root) {
    if (!root) return true;
    return dfs(root->left, root->right);
}
bool dfs(TreeNode* p, TreeNode* q) {}

步骤二:得出深搜终止条件

//bool isSymmetric(TreeNode* root) {
//  if (!root) return true;
//  return dfs(root->left, root->right);
//}
//bool dfs(TreeNode* p, TreeNode* q) {
    if (!p && !q) return true;
    if (p && !q || !p && q) return false;
    if (p->val != q->val) return false;
//}

步骤三:考虑深搜的方法:从根结点的左右子节点开始,左结点的左儿子等于右结点的右儿子,左结点的右儿子等于右结点的左儿子

//bool isSymmetric(TreeNode* root) {
//  if (!root) return true;
//  return dfs(root->left, root->right);
//}
//bool dfs(TreeNode* p, TreeNode* q) {
//  if (!p && !q) return true;
//  if (p && !q || !p && q) return false;
//  if (p->val != q->val) return false;
    return dfs(p->left, q->right) && dfs(p->right, q->left);
//}

迭代法:类比迭代法进行中序遍历的操作

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:16 MB, 在所有 C++ 提交中击败了9.01%的用户

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (!root) return true;
        stack<TreeNode*> l_stk, r_stk;
        TreeNode* l_root = root->left, * r_root = root->right;
        while (l_root || r_root || l_stk.size() || r_stk.size()) {
            while (l_root && r_root) {
                l_stk.push(l_root), r_stk.push(r_root);
                l_root = l_root->left, r_root = r_root->right;
            }
            if (l_root || r_root) return false;
            l_root = l_stk.top(), r_root = r_stk.top();
            l_stk.pop(), r_stk.pop();
            if (l_root->val != r_root->val) return false;
            l_root = l_root->right, r_root = r_root->left;
        }
        return true;
    }
};

105. 从前序与中序遍历序列构造二叉树(中等)

105. 从前序与中序遍历序列构造二叉树

102. 二叉树的层序遍历(中等)

102. 二叉树的层序遍历

使用队列:

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:12.3 MB, 在所有 C++ 提交中击败了9.05%的用户

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
       vector<vector<int>> res;
       if (!root) return res;
       queue<pair<TreeNode*, int>>q;
       q.push(make_pair(root, 0));
        while (q.size()) {
            TreeNode* node = q.front().first;
            int level = q.front().second;
            q.pop();
            if (res.size() <= level) res.push_back({});
            res[level].push_back(node->val);
            if (node->left) q.push(make_pair(node->left, level + 1));
            if (node->right) q.push(make_pair(node->right, level + 1));
        }
        return res;
    }
};

执行用时:4 ms, 在所有 C++ 提交中击败了84.05%的用户

内存消耗:12.3 MB, 在所有 C++ 提交中击败了9.46%的用户

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
       vector<vector<int>> res;
       if (!root) return res;
       queue<TreeNode*> q;
       q.push(root);
       while (q.size()) {
           int len = q.size();
           vector<int> level;
           for (int i = 0; i < len; i++) {
               TreeNode* node = q.front();
               q.pop();
               level.push_back(node->val);
               if (node->left) q.push(node->left);
               if (node->right) q.push(node->right); 
           }
           res.push_back(level);
       }
       return res;
    }
};

n false;
return dfs(p->left, q->right) && dfs(p->right, q->left);
//}






迭代法:类比迭代法进行中序遍历的操作

>执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
>
>内存消耗:16 MB, 在所有 C++ 提交中击败了9.01%的用户

~~~cpp
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (!root) return true;
        stack<TreeNode*> l_stk, r_stk;
        TreeNode* l_root = root->left, * r_root = root->right;
        while (l_root || r_root || l_stk.size() || r_stk.size()) {
            while (l_root && r_root) {
                l_stk.push(l_root), r_stk.push(r_root);
                l_root = l_root->left, r_root = r_root->right;
            }
            if (l_root || r_root) return false;
            l_root = l_stk.top(), r_root = r_stk.top();
            l_stk.pop(), r_stk.pop();
            if (l_root->val != r_root->val) return false;
            l_root = l_root->right, r_root = r_root->left;
        }
        return true;
    }
};

105. 从前序与中序遍历序列构造二叉树(中等)

105. 从前序与中序遍历序列构造二叉树

102. 二叉树的层序遍历(中等)

102. 二叉树的层序遍历

使用队列:

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户

内存消耗:12.3 MB, 在所有 C++ 提交中击败了9.05%的用户

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
       vector<vector<int>> res;
       if (!root) return res;
       queue<pair<TreeNode*, int>>q;
       q.push(make_pair(root, 0));
        while (q.size()) {
            TreeNode* node = q.front().first;
            int level = q.front().second;
            q.pop();
            if (res.size() <= level) res.push_back({});
            res[level].push_back(node->val);
            if (node->left) q.push(make_pair(node->left, level + 1));
            if (node->right) q.push(make_pair(node->right, level + 1));
        }
        return res;
    }
};

执行用时:4 ms, 在所有 C++ 提交中击败了84.05%的用户

内存消耗:12.3 MB, 在所有 C++ 提交中击败了9.46%的用户

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
       vector<vector<int>> res;
       if (!root) return res;
       queue<TreeNode*> q;
       q.push(root);
       while (q.size()) {
           int len = q.size();
           vector<int> level;
           for (int i = 0; i < len; i++) {
               TreeNode* node = q.front();
               q.pop();
               level.push_back(node->val);
               if (node->left) q.push(node->left);
               if (node->right) q.push(node->right); 
           }
           res.push_back(level);
       }
       return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于LSTM的财务因子预测选股模型LSTM (Long Short-Term Memory) 是一种特殊的循环神经网络(RNN)架构,用于处理具有长期依赖关系的序列数据。传统的RNN在处理长序列时往往会遇到梯度消失或梯度爆炸的问题,导致无法有效地捕捉长期依赖。LSTM通过引入门控机制(Gating Mechanism)和记忆单元(Memory Cell)来克服这些问题。 以下是LSTM的基本结构和主要组件: 记忆单元(Memory Cell):记忆单元是LSTM的核心,用于存储长期信息。它像一个传送带一样,在整个链上运行,只有一些小的线性交互。信息很容易地在其上保持不变。 输入门(Input Gate):输入门决定了哪些新的信息会被加入到记忆单元中。它由当前时刻的输入和上一时刻的隐藏状态共同决定。 遗忘门(Forget Gate):遗忘门决定了哪些信息会从记忆单元中被丢弃或遗忘。它也由当前时刻的输入和上一时刻的隐藏状态共同决定。 输出门(Output Gate):输出门决定了哪些信息会从记忆单元中输出到当前时刻的隐藏状态中。同样地,它也由当前时刻的输入和上一时刻的隐藏状态共同决定。 LSTM的计算过程可以大致描述为: 通过遗忘门决定从记忆单元中丢弃哪些信息。 通过输入门决定哪些新的信息会被加入到记忆单元中。 更新记忆单元的状态。 通过输出门决定哪些信息会从记忆单元中输出到当前时刻的隐藏状态中。 由于LSTM能够有效地处理长期依赖关系,它在许多序列建模任务中都取得了很好的效果,如语音识别、文本生成、机器翻译、时序预测等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值