首页 > 其他分享 >[LeetCode] 数据结构入门

[LeetCode] 数据结构入门

时间:2023-03-19 18:23:23浏览次数:39  
标签:return 入门 val int nullptr 节点 数据结构 root LeetCode

数据结构入门

217 存在重复元素

给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false

解法 1:两层循环

第一层循环遍历数组元素,第二层循环同样遍历数组元素,当两者相同时返回 true。超时。

解法 2:带存储的循环

创建临时数组。循环遍历数组元素,再遍历临时数组,如果临时数组中没有该元素,则加入数组中。

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        vector<int> bases{nums[0]};
        for (int i = 1; i < nums.size(); ++i) {
            for (int base : bases) {
                if (nums[i] == base) {
                    return true;
                } else {
                    bases.push_back(nums[i]);
                }
            }
        }
        return false;
    }
};

执行出错信息 ERROR: AddressSanitizer: heap-use-after-free。错误在于当使用了 push_back() 函数后,vector 可能因为容量不足而创建新的向量对象,这时之前的对象已经被销毁,引用自然也失效了。但该问题仅出现在 LeetCode 网站编译器上,本地 MSVC 编译成功且可以运行。即使成功应该也会导致超时。

解法 3:排序后查找

将数组排序,再依次比较判断是否有重复元素。

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        for (int i = 0; i < n - 1; i++) {
            if (nums[i] == nums[i + 1]) {
                return true;
            }
        }
        return false;
    }
};

解法 4:哈希表

将每个元素依次在哈希表中查找,如果没有则加入哈希表中。

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_set<int> s;
        for (int x: nums) {
            if (s.find(x) != s.end()) {
                return true;
            }
            s.insert(x);
        }
        return false;
    }
};

53 最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

解法 1:动态规划

动态规划的关键在于递归地求定义最优解的值,然后采用自底向上的方法求解。

用 \(f(i)\) 表示以第 \(i\) 个数结尾的连续子数组的最大和,那么 \(f(i)=\max(f(i-1)+nums[i],nums[i])\)。

最大值要么为前一个数组的最大值加上后一个元素,要么只是后一个元素。

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

需要注意的是最后求的是最大值,需要准备元素判断并保存最大值。

1 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

解法 1:哈希表

创建一个哈希表保存所有数据,再遍历哈希表找是否有元素与当前遍历值和为 target 的元素。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> ans{};
        unordered_map<int, int> map{};
        for (int i = 0; i < nums.size(); ++i) {
            map.insert({nums[i], i});
        }

        for (int i = 0; i < nums.size(); ++i) {
            if (map.find(target - nums[i]) != map.end()) {
                int second = map.find(target - nums[i])->second;
                if (i != second) {
                    ans.push_back(i);
                    ans.push_back(second);
                    return ans;
                }
            }
        }
        return ans;
    }
};

注意在 unordered_map 中,pair 的前一个值为 find() 函数的目标,后一个值为附带的信息。还需要注意防止 [3, 2, 4] 导致的重复数据。

88 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1nums2,另有两个整数 mn ,分别表示 nums1nums2 中的元素数目。

请你 合并 nums2nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n

解法 1:复制数组再合并

在直接将两数组合并时发现难以控制中途的各种问题,于是构造一个新的 nums1 再进行合并。

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        vector<int> temp_nums1(m);
        copy(nums1.begin(), nums1.begin() + m, temp_nums1.begin());

        int nums1_index = 0;
        int nums2_index = 0;
        int i = 0;
        while (nums1_index != m && nums2_index != n) {
            if (temp_nums1[nums1_index] >= nums2[nums2_index]) {
                nums1[i] = nums2[nums2_index];
                nums2_index++;
            } else {
                nums1[i] = temp_nums1[nums1_index];
                nums1_index++;
            }
            i++;
        }

        int final_index = nums1_index + nums2_index;
        if (nums1_index == m) {
            copy(nums2.begin() + nums2_index, nums2.end(), nums1.begin() + final_index);
        }
        if (nums2_index == n) {
            copy(temp_nums1.begin() + nums1_index, temp_nums1.end(), nums1.begin() + final_index);
        }
    }
};

解法 2:合并后排序

直接复制合并后使用内置 sort() 方法排序。

350 两个数组的交集 II

给你两个整数数组 nums1nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。

解法 1:哈希表

将第一个数组中每个出现的数存入哈希表并记录出现的次数,再遍历第二个数组,如果表中存在当前遍历的元素,则将该数加入结果中并将哈希表中的记录次数减一。

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        if (nums1.empty() || nums2.empty()) {
            return vector<int>{};
        }

        unordered_map<int, int> map;
        for (int x : nums1) {
            auto iter = map.find(x);
            if (iter == map.end()) {
                map.insert({x, 1});
            } else {
                iter->second++;
            }
        }

        vector<int> ans{};
        for (int x : nums2) {
            auto iter = map.find(x);
            if (iter != map.end()) {
                ans.emplace_back(x);
                iter->second--;
                if (iter->second == 0) {
                    map.erase(x);
                }
            }
        }

        return ans;
    }
};

注意观察题目是否要求连续,map 中元素的 second 可以用来记录额外的值,这道题记录了次数。

121 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

解法 1:暴力破解

对于第 i 天卖出的股票来说,可以获得的最大利润为第 i 天价格减掉前 i-1 天中最低价格的差。计算出每个第 i 天卖出时的最高利润比较即可。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int *arr = new int[prices.size()]{0};
        arr[0] = 0;
        set<int> min_set;
        min_set.insert(prices[0]);

        for (int i = 1; i < prices.size(); ++i) {
            min_set.insert(prices[i]);
            arr[i] = prices[i] - *min_set.begin();
        }

        int ans = 0;
        for (int i = 0; i < prices.size(); ++i) {
            ans = ans > arr[i] ? ans : arr[i];
        }
        return ans;
    }
};

可以优化的地方在于程序只需要前 i-1 个数中最小的值,因此记录一个 minimum 即可,有点类似动态规划,将上一步计算的值保留到下一步,而不需要一整个完成的 set

解法 2:一次遍历

主要思想同上,但如果当前值为前 i-1 天中的最小值则,否则直接计算当天卖出可以获得的利润,保存到最大值中。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int max_profit = 0;
        int minimum = prices[0];

        for (int i = 1; i < prices.size(); ++i) {
            if (prices[i] < minimum) {
                minimum = prices[i];
            } else {
                max_profit = max(prices[i] - minimum, max_profit);
            }
        }
        return max_profit;
    }
};

566 重塑矩阵

在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。

给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 rc ,分别表示想要的重构的矩阵的行数和列数。

重构后的矩阵需要将原始矩阵的所有元素以相同的 行遍历顺序 填充。

如果具有给定参数的 reshape 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

解法 1:复制

将原矩阵复制到一个新向量中,再从向量复制到要求的矩阵中。

class Solution {
public:
    vector<vector<int>> matrixReshape(vector<vector<int>>& mat, int r, int c) {
        vector<int> arr{};
        for (vector<int> outer : mat) {
            for (int x : outer) {
                arr.emplace_back(x);
            }
        }

        if (arr.size() != r * c) {
            return mat;
        }

        vector<vector<int>> ans{};
        auto iter = arr.begin();
        for (int i = 0; i < r; ++i) {
            vector<int> temp(c);
            copy(iter, iter + c, temp.begin());
            ans.emplace_back(temp);
            iter = iter + c;
        }
        return ans;
    }
};

解法 2:映射

将原数组元素按照下标映射到新的数组中。

118 杨辉三角

给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。

在「杨辉三角」中,每个数是它左上方和右上方的数的和。

解法 1:根据题意写

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> ans{};

        ans.emplace_back(vector<int>{1});
        if (numRows == 1) {
            return ans;
        }

        ans.emplace_back(vector<int>{1,1});
        if (numRows == 2) {
            return ans;
        }

        for (int i = 3; i <= numRows; ++i) {
            vector<int> temp(i);
            temp[0] = 1;
            for (int j = 1; j < i - 1; ++j) {
                temp[j] = ans[i - 2][j - 1] + ans[i - 2][j];
            }
            temp[i - 1] = 1;
            ans.emplace_back(temp);
        }

        return ans;
    }
};

解法 2:数学

根据杨辉三角的用途利用二项式定理计算每个元素数据。

36 有效的数独

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

注意:

  • 一个有效的数独(部分已被填充)不一定是可解的。
  • 只需要根据以上规则,验证已经填入的数字是否有效即可。
  • 空白格用 '.' 表示。

解法 1:三次遍历

将提供的元素遍历三遍,依次检查行、列和小格子。

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        for (int i = 0; i < 9; ++i) {
            set<char> column_set{};
            for (int j = 0; j < 9; ++j) {
                char c = board[i][j];
                if (c == '.') {
                    continue;
                }
                if (!column_set.insert(c).second) {
                    return false;
                }
            }
        }

        for (int i = 0; i < 9; ++i) {
            set<char> line_set{};
            for (int j = 0; j < 9; ++j) {
                char c = board[j][i];
                if (c == '.') {
                    continue;
                }
                if (!line_set.insert(c).second) {
                    return false;
                }
            }
        }

        for (int i = 0; i < 9; i = i + 3) {
            for (int j = 0; j < 9; j = j + 3) {
                set<char> block_set{};
                for (int m = i; m < i + 3; ++m) {
                    for (int n = j; n < j + 3; ++n) {
                        char c = board[m][n];
                        if (c == '.') {
                            continue;
                        }
                        if (!block_set.insert(c).second) {
                            return false;
                        }
                    }
                }
            }
        }

        return true;
    }
};

解法 2:一次遍历

一次遍历转换下标依次存到不同的哈希表中。

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        vector<set<int>> line_sets(9);
        vector<set<int>> column_sets(9);
        vector<set<int>> block_sets(9);

        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                char c = board[i][j];
                if (c == '.') {
                    continue;
                }
                if (!line_sets[i].insert(c).second) {
                    return false;
                }
                if (!column_sets[j].insert(c).second) {
                    return false;
                }
                if (!block_sets[(i / 3) * 3 + j / 3].insert(c).second) {
                    return false;
                }
            }
        }
        return true;
    }
};

73 矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。

解法 1:遍历并存储 0 元素

使用两个哈希表保存 0 所在的行和列,再将遍历它们并将表中对应行列元素设置为 0。

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        unordered_set<int> line_set{};
        unordered_set<int> column_set{};

        for (int i = 0; i < matrix.size(); ++i) {
            for (int j = 0; j < matrix[0].size(); ++j) {
                if (matrix[i][j] == 0) {
                    line_set.insert(i);
                    column_set.insert(j);
                }
            }
        }

        for (int x : line_set) {
            for (int j = 0; j < matrix[0].size(); ++j) {
                matrix[x][j] = 0;
            }
        }
        for (int x : column_set) {
            for (int i = 0; i < matrix.size(); ++i) {
                matrix[i][x] = 0;
            }
        }
    }
};

解法 2:两个标记变量

将第一列和第一行是否有 0 存入两个变量中,再第一行和第一列当作前面的 set 使用,最后根据两个变量决定第一行和第一列是否设置为 0。

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

给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1

解法 1:哈希表

将每个字符依次加入到哈希表中,如果字符已经存在则记录值加一,最后遍历哈希表找到记录次数为 1 的不重复字符串,再查找该字符在字符串中的位置找到第一个。

class Solution {
public:
    int firstUniqChar(string s) {
        unordered_map<char, int> count_map{};
        unordered_map<char, int> index_map{};
        for (int i = 0; i < s.size(); ++i) {
            char c = s[i];
            if (count_map.find(c) == count_map.end()) {
                count_map.insert({c, 1});
                index_map.insert({c, i});
            } else {
                count_map.find(c)->second++;
            }
        }

        int min_index = s.size();
        unordered_map<char, int>::iterator iter;
        for (iter = count_map.begin(); iter != count_map.end(); ++iter) {
            if (iter->second == 1) {
                int current_index = index_map.find(iter->first)->second;
                min_index = min(current_index, min_index);
            }
        }
        return (min_index == s.size() ? -1 : min_index);
    }
};

解法 2:优化的哈希表

只使用一个哈希表记录元素和它的位置。遍历字符串时,如果不存在该字符,则保存字符和它的位置;如果存在则将字符的位置设置为 -1。最后遍历哈希表找到位置不是 -1 的第一个字符位置。

class Solution {
public:
    int firstUniqChar(string s) {
        unordered_map<char, int> map{};
        for (int i = 0; i < s.size(); ++i) {
            char c = s[i];
            if (map.find(c) == map.end()) {
                map.insert({c, i});
            } else {
                map.find(c)->second = -1;
            }
        }

        int min_index = s.size();
        unordered_map<char, int>::iterator iter;
        for (iter = map.begin(); iter != map.end(); ++iter) {
            if (iter->second != -1) {
                int current_index = iter->second;
                min_index = min(current_index, min_index);
            }
        }
        return (min_index == s.size() ? -1 : min_index);
    }
};

383 赎金信

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

解法 1:哈希表

magazine 的字母存入哈希表中并记录出现的次数。再遍历 ransomNote,如果哈希表无当前字符,返回 false;如果有,记录次数减一,如果记录次数小于 0,返回 false

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        unordered_map<char, int> map{};
        for (char c : magazine) {
            if (map.find(c) == map.end()) {
                map.insert({c, 1});
            } else {
                map.find(c)->second++;
            }
        }
        for (char c : ransomNote) {
            if (map.find(c) == map.end()) {
                return false;
            } else {
                map.find(c)->second--;
                if (map.find(c)->second < 0) {
                    return false;
                }
            }
        }
        return true;
    }
};

242 有效的字母异位词

给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:st 中每个字符出现的次数都相同,则称 st 互为字母异位词。

解法 1:哈希表

将两个字符串中的字符存入两个哈希表中,再比较两个哈希表即可。

class Solution {
public:
    bool isAnagram(string s, string t) {
        unordered_map<char, int> map_s;
        unordered_map<char, int> map_t;
        for (char c : s) {
            if (map_s.find(c) == map_s.end()) {
                map_s.insert({c, 1});
            } else {
                map_s.find(c)->second++;
            }
        }
        for (char c : t) {
            if (map_t.find(c) == map_t.end()) {
                map_t.insert({c, 1});
            } else {
                map_t.find(c)->second++;
            }
        }

        for (pair<char, int> p : map_s) {
            if (map_t.find(p.first) != map_t.end() && map_t.find(p.first)->second == p.second) {
                map_t.erase(p.first);
            } else {
                return false;
            }
        }
        return map_t.empty();
    }
};

141 环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false

解法 1:哈希表

遍历链表,将读过的节点存入哈希表中,如果表中已存在该节点,返回 true,否则加入节点。

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

解法 2:Floyd 判圈算法

设置快慢两个指针,慢指针每次前进一步,快指针每次前进两步,如果链表中有环,快指针将优先进入环,并在某个时刻与慢指针重合。当快指针为空指针时链表不是环形链表。

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

注意需要判断多个指针是否为空,否则将无法访问指向的下一个指针。

21 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

解法 1:改变指针合并

首先判断两个链表的第一个节点选做新链表的链表头,保留两个链表变量记录当前两链表访问的位置,再进行修改指针的合并。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* index1 = list1;
        ListNode* index2 = list2;
        auto *ans = new ListNode(0);
        auto *root = ans;
        while (index1 != nullptr && index2 != nullptr) {
            if (index1->val >= index2->val) {
                ans->next = index2;
                index2 = index2->next;
            } else {
                ans->next = index1;
                index1 = index1->next;
            }
            ans = ans->next;
        }
        if (index1 == nullptr) {
            ans->next = index2;
        }
        if (index2 == nullptr) {
            ans->next = index1;
        }
        return root->next;
    }
};

解法 2:递归

函数返回的是合并链表后最小的节点,可以利用这一点进行递归,得到当前最小的节点后连接到剩余两个链表的最小节点,直到两链表遍历到结尾。

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

很难自己想出来。

203 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

解法 1:常规方法

判断三种情况,删除头部节点,中部节点,尾部节点。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        if (head == nullptr) {
            return nullptr;
        }
        while (head->val == val) {
            head = head->next;
            if (head == nullptr) {
                return nullptr;
            }
        }
        ListNode* root = head;

        while (head != nullptr) {
            if (head->next == nullptr) {
                return root;
            }
            if (head->next->val == val) {
                head->next = head->next->next;
            } else {
                head = head->next;
            }
        }
        return root;
    }
};

注意每个使用 node->next->val 的地方前检查 node->next 是否为空指针。小技巧:添加一个头部节点即可将原头部节点处理为中部节点。

206 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

解法 1:存储数据新建链表

将链表读取一遍并保存在数组中,再倒序遍历数组创建链表。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        vector<int> arr{};
        while (head != nullptr) {
            arr.emplace_back(head->val);
            head = head->next;
        }
        auto root = new ListNode{0};
        ListNode* origin = root;
        for (int i = 0; i < arr.size(); ++i) {
            auto node = new ListNode(arr[arr.size() - i - 1]);
            root->next = node;
            root = root->next;
        }
        return origin->next;
    }
};

解法 2:迭代

按照题意存储当前节点和后一个节点,使后一个节点的 next 指向前一个节点即可。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *pre = nullptr;
        if (head == nullptr) {
            return nullptr;
        }
        ListNode *now = head;
        if (head->next == nullptr) {
            return now;
        }
        ListNode *next = head->next;
        while (now->next != nullptr) {
            now->next = pre;
            pre = now;
            now = next;
            next = next->next;
        }
        now->next = pre;
        return now;
    }
};

83 删除排序链表中的重复元素

给定一个已排序的链表的头 head删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表

解法 1:删除重复节点

设置比较指针和遍历指针,将遍历指针与比较指针对比判断是否为重复数组。

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode *pre = head;
        if(pre == nullptr) {
            return nullptr;
        }
        ListNode *now = head->next;
        while(now != nullptr) {
            while (pre->val == now->val) {
                pre->next = now->next;
                now = now->next;
                if (now == nullptr) {
                    return head;
                }
            }
            pre = pre->next;
            now = now->next;
        }
        return head;
    }
};

解法 2:对比删除不保留数据

取消保存 now 变量,直接将 pre->valpre->next->val 对比。

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode *pre = head;
        if(pre == nullptr) {
            return nullptr;
        }
        while (pre->next != nullptr) {
            while (pre->val == pre->next->val) {
                pre->next = pre->next->next;
                if (pre->next == nullptr) {
                    return head;
                }
            }
            pre = pre->next;
        }
        return head;
    }
};

20 有效的括号

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

解法 1:栈

将左括号进栈,右括号出栈,如果最后栈为空则有效。

class Solution {
public:
    bool isValid(string s) {
        if (s.size() % 2 == 1) {
            return false;
        }
        stack<char> stack{};
        for (char c : s) {
            if (c == '(' || c == '[' || c == '{') {
                stack.push(c);
            } else if (stack.empty()) {
                return false;
            }

            if (c == ')') {
                if (stack.top() != '(') {
                    return false;
                } else {
                    stack.pop();
                }
            } else if (c == ']') {
                if (stack.top() != '[') {
                    return false;
                } else {
                    stack.pop();
                }
            } else if (c == '}') {
                if (stack.top() != '{') {
                    return false;
                } else {
                    stack.pop();
                }
            }
        }
        return stack.empty();
    }
};

注意需要判断第一个符号即为反括号的情况,这时 stack.pop() 会报错。

232 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

  • 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

解法 1:两个栈互相导

将第一个栈中的数据导入到第二个栈,这时最下方的数据就被移动到了最上方,执行操作后再将数据导回原栈即可。

class MyQueue {
public:
    stack<int> real_stack;
    stack<int> ref_stack;

    MyQueue() {
        real_stack = stack<int>{};
        ref_stack = stack<int>{};
    }

    void push(int x) {
        real_stack.push(x);
    }

    int pop() {
        if (empty()) {
            return -1;
        }
        while (!real_stack.empty()) {
            int x = real_stack.top();
            ref_stack.push(x);
            real_stack.pop();
        }
        int ans = ref_stack.top();
        ref_stack.pop();
        while (!ref_stack.empty()) {
            int x = ref_stack.top();
            real_stack.push(x);
            ref_stack.pop();
        }
        return ans;
    }

    int peek() {
        while (!real_stack.empty()) {
            int x = real_stack.top();
            ref_stack.push(x);
            real_stack.pop();
        }
        int ans = ref_stack.top();
        while (!ref_stack.empty()) {
            int x = ref_stack.top();
            real_stack.push(x);
            ref_stack.pop();
        }
        return ans;
    }

    bool empty() {
        return real_stack.empty();
    }
};

144 二叉树的前序遍历

解法 1:递归

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans{};
        traverse(root, ans);
        return ans;
    }

    void traverse(TreeNode* node, vector<int> &v) {
        if (node == nullptr) {
            return;
        } else {
            v.emplace_back(node->val);
            traverse(node->left, v);
            traverse(node->right, v);
        }
    }
};

解法 2:迭代

利用一个栈维护当前访问的根节点。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ans{};
        if (root == nullptr) {
            return ans;
        }

        stack<TreeNode*> stack{};
        stack.emplace(root);

        while (!stack.empty()) {
            while (root != nullptr) {
                ans.emplace_back(root->val);
                stack.emplace(root);
                root = root->left;
            }
            root = stack.top();
            stack.pop();
            root = root->right;
        }

        return ans;
    }
};

94 二叉树的中序遍历

解法 1:递归

同上,调整三条语句。

解法 2:迭代

同上。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans{};
        if (root == nullptr) {
            return ans;
        }

        stack<TreeNode*> stack{};

        while (!stack.empty() || root != nullptr) {
            while (root != nullptr) {
                stack.emplace(root);
                root = root->left;
            }
            root = stack.top();
            ans.emplace_back(root->val);
            stack.pop();
            root = root->right;
        }
        return ans;
    }
};

145 二叉树的后序遍历

解法 1:递归

同上,调整三条语句。

解法 2:迭代

同上。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ans{};
        if (root == nullptr) {
            return ans;
        }

        stack<TreeNode*> stack{};
        TreeNode* prev = nullptr;
        while (!stack.empty() || root != nullptr) {
            while (root != nullptr) {
                stack.emplace(root);
                root = root->left;
            }
            root = stack.top();
            stack.pop();
            if (root->right == nullptr || root->right == prev) {
                ans.emplace_back(root->val);
                prev = root;
                root = nullptr;
            } else {
                stack.emplace(root);
                root = root->right;
            }
        }
        return ans;
    }
};

102 二叉树的层序遍历

解法 1:广度优先搜索

建立一个队列,将当前的节点放入队列中,将当前左右子节点依次加入队列中,然后移除原始节点,依次执行直到队列为空。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans{};
        if (root == nullptr) {
            return ans;
        }

        queue<TreeNode*> queue{};
        queue.push(root);
        while (!queue.empty()) {
            int count = queue.size();
            vector<int> v{};
            for (int i = 0; i < count; ++i) {
                TreeNode* node = queue.front();
                v.emplace_back(node->val);
                if (node->left != nullptr) {
                    queue.push(node->left);
                }
                if (node->right != nullptr) {
                    queue.push(node->right);
                }
                queue.pop();
            }
            ans.emplace_back(v);
        }
        return ans;
    }
};

广度优先搜索的核心即在于构建一个队列,利用先进先出的原理存储节点数据。

104 二叉树的最大深度

解法 1:递归

节点的最大深度必然为左叶子节点和有叶子节点的最大值加一。

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr) {
            return 0;
        } else if (root->left == nullptr && root->right == nullptr) {
            return 1;
        } else if (root->left == nullptr) {
            return maxDepth(root->right) + 1;
        } else if (root->right == nullptr) {
            return maxDepth(root->left) + 1;
        } else {
            return max(maxDepth(root->left), maxDepth(root->right)) + 1;
        }
    }
};

注意,不需要判断左右指针是否为空,因为第一个条件已经判断了。这也是深度优先的思想。

解法 2:广度优先搜索

方法与层序遍历一致,记录并保留最大值。

101 对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

解法 1:递归

如果一个根节点发展出的树轴对称,则它的左右节点都相等且对称。对称指左叶子节点的左叶子节点要和右叶子节点的右叶子节点相等,且左叶子节点的右叶子节点和右叶子节点的左叶子节点相等。

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        return check(root, root);
    }

    bool check(TreeNode* left, TreeNode* right) {
        if (left == nullptr && right != nullptr) {
            return false;
        } else if (left != nullptr && right == nullptr) {
            return false;
        } else if (left == nullptr && right == nullptr) {
            return true;
        } else {
            return left->val == right->val && check(left->left, right->right) && check(left->right, right->left);
        }
    }
};

226 翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

解法 1:递归

翻转二叉树也就是把树的左右叶子节点交换,并交换它们的左右叶子节点。

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == nullptr) {
            return nullptr;
        }
        TreeNode* left = root->right;
        TreeNode* right = root->left;
        root->left = invertTree(left);
        root->right = invertTree(right);
        return root;
    }
};

112 路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

叶子节点 是指没有子节点的节点。

解法 1:递归

深度遍历树,如果遍历到结尾且路径和为要求值则返回 true

class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        return traverse(root, 0, targetSum);
    }

    bool traverse(TreeNode* node, int sum, int targetSum) {
        if (node == nullptr) {
            return false;
        }
        sum = sum + node->val;
        if (sum == targetSum) {
            if (node->left == nullptr && node->right == nullptr) {
                return true;
            }
        }
        return traverse(node->left, sum, targetSum) || traverse(node->right, sum, targetSum);
    }
};

注意,可以简化为一个函数,每次更新时将 targetSum 改为 targetSum - root->val 即可。

700 二叉搜索树中的搜索

给定二叉搜索树(BST)的根节点 root 和一个整数值 val

你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null

解法 1:定义

二叉搜索树左节点小于父节点,右节点大于父节点。

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        while (root != nullptr) {
            if (val == root->val) {
                return root;
            } else if (val < root->val) {
                root = root->left;
            } else {
                root = root->right;
            }
        }
        return nullptr;
    }
};

701 二叉搜索树中的插入操作

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果

解法 1:定义

根据定义插入值。

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        auto new_node = new TreeNode(val);
        if (root == nullptr) {
            return new_node;
        }

        TreeNode* ans = root;
        TreeNode* pre = nullptr;
        while (root != nullptr) {
            pre = root;
            if (val > root->val) {
                root = root->right;
            } else {
                root = root->left;
            }
        }
        if (val > pre->val) {
            pre->right = new_node;
        } else {
            pre->left = new_node;
        }
        return ans;
    }
};

98 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

解法 1:错误递归

如果二叉树某节点为有效的,那么它的左右子树都应该是有效的。

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        if (root == nullptr) {
            return true;
        }
        bool left = true;
        if (root->left != nullptr) {
            left = root->val > root->left->val && isValidBST(root->left);
        }
        bool right = true;
        if (root->right != nullptr) {
            right = root->val < root->right->val && isValidBST(root->right);
        }
        return left && right;
    }
};

解答错误。仅仅比较了节点与其左右子节点的大小关系。

解法 2:递归

左子树所有的节点都应该小于根节点,右子树的所有节点都应该大于根节点。

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        return traverse(root, LONG_MIN, LONG_MAX);
    }

    bool traverse(TreeNode* node, long min, long max) {
        if (node == nullptr) {
            return true;
        }
        if (node->val <= min || node->val >= max) {
            return false;
        } else {
            return traverse(node->left, min, node->val) && traverse(node->right, node->val, max);
        }
    }
};

当有节点值等于根节点值时也不是有效的二叉搜索树。还需要注意输入数值的取值范围,这里只能使用 long

解法 3:中序遍历

当二叉搜索树中序遍历后,形成的序列应该为从小到大依次排列。

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        vector<long> v{};
        traverse(root, v);
        for (int i = 0; i < v.size() - 1; ++i) {
            if (v[i + 1] <= v[i]) {
                return false;
            }
        }
        return true;
    }

    void traverse(TreeNode* node, vector<long> &v) {
        if (node == nullptr) {
            return;
        }
        traverse(node->left, v);
        v.emplace_back(node->val);
        traverse(node->right, v);
    }
};

执行效率低。

653 两数之和 IV - 输入二叉搜索树

给定一个二叉搜索树 root 和一个目标结果 k,如果二叉搜索树中存在两个元素且它们的和等于给定的目标结果,则返回 true

解法 1:遍历与哈希表

遍历树中元素并存入哈希表中,遍历哈希表检测是否有符合条件的两个元素。

class Solution {
public:
    bool findTarget(TreeNode* root, int k) {
        unordered_set<int> set{};
        traverse(root, set);
        for (int x : set) {
            if (set.find(k - x) != set.end() && *set.find(k - x) != x) {
                return true;
            }
        }
        return false;
    }

    void traverse(TreeNode* node, unordered_set<int> &set) {
        if (node == nullptr) {
            return;
        }
        traverse(node->left, set);
        set.insert(node->val);
        traverse(node->right, set);
    }
};

注意需要排除找到的元素与自己相同的情况,比如输入 [1], 2

解法 2:中序遍历与双指针

首先将二叉搜索树中序遍历转换为有序数组,再使用头尾两个指针查找是否有满足要求的两个元素。

class Solution {
public:
    bool findTarget(TreeNode* root, int k) {
        vector<int> vector{};
        traverse(root, vector);
        int head = 0;
        int end = vector.size() - 1;
        while (head != end) {
            if (vector[head] + vector[end] > k) {
                end--;
            } else if (vector[head] + vector[end] < k) {
                head++;
            } else {
                return true;
            }
        }
        return false;
    }

    void traverse(TreeNode* node, vector<int> &v) {
        if (node == nullptr) {
            return;
        }
        traverse(node->left, v);
        v.emplace_back(node->val);
        traverse(node->right, v);
    }
};

235 二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

解法 1:递归

从根节点开始,如果两个值都小于根节点,则将根节点设置为根节点的左节点进行递归,大于则设置为右节点。当前节点与左右节点中任意一个相同时或当前节点为 p、q 中间值则结束递归。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr || p == nullptr || q == nullptr) {
            return nullptr;
        }
        if (p->val > q->val) {
            TreeNode* t = p;
            p = q;
            q = t;
        }
        if (root == p || root == q) {
            return root;
        }
        if (root->val > p->val && root->val < q->val) {
            return root;
        }
        if (p->val > root->val) {
            return lowestCommonAncestor(root->right, p, q);
        }
        if (q->val < root->val) {
            return lowestCommonAncestor(root->left, p, q);
        }
        return nullptr;
    }
};

解法 2:迭代

也可以从根节点开始迭代地往下查找。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr || p == nullptr || q == nullptr) {
            return nullptr;
        }
        while (root != p && root != q && !(root->val < max(p->val, q->val) && root->val > min(p->val, q->val))) {
            if (root->val > max(p->val, q->val)) {
                root = root->left;
            } else {
                root = root->right;
            }
        }
        return root;
    }
};

标签:return,入门,val,int,nullptr,节点,数据结构,root,LeetCode
From: https://www.cnblogs.com/futureknight/p/17233846.html

相关文章

  • CSS入门
    1.CSS简介CSS的主要使用场景就是美化网页,布局页面。1.1HTML的局限性说起HTML,其实就是个非常单纯的家伙,他只关注内容语义。比如<h1>表明这是一个大标题,<p>表明这是一个......
  • 深度学习入门 Chapter2
    What'sperceptronalgorithminventedbyFrankRosenblatt?Theperceptronalgorithmisasupervisedlearningalgorithmforbinaryclassificationofinputdatai......
  • java——Zookeeper学习——入门学习
    学习之前看了2个B站教程:   1、千峰:https://www.bilibili.com/video/BV1Ph411n7Ep/?vd_source=79bbd5b76bfd74c2ef1501653cee29d6   2、黑马:https://www.bili......
  • Hadoop入门
    目录1️⃣、Hadoop概述1.1、Hadoop是什么1.2、三大发行版本1.3、优势1.4、组成HDFSYARNMapReduceHDFS、YARN、MapReduce三者关系1.6、大数据技术生态体系2️⃣、Hadoop运行环境......
  • Django笔记二之连接数据库、执行migrate数据结构更改操作
    本篇笔记目录索引如下:Django连接mysql,执行数据库表结构迁移步骤介绍操作数据库,对数据进行简单操作接下来几篇笔记都会介绍和数据库相关,包括数据库的连接、操作(包括增......
  • Tomcat 入门实战(2)--Tomcat Native Library 使用
     本文主要介绍 TomcatNativeLibrary安装及使用,文中所使用到的软件版本:Centos7.9.2009、Java1.8.0_321、Tomcat8.5.84、APR1.7.0。1、APR1.1、APR简介APR(Apac......
  • 【Netty入门和实践】1.传统的socket分析
    我们知道,使用Java进行TCP/UDP协议的网络通信一般使用Java的Net包下的Socket服务进行编写,有Server服务端和Client客户端,服务端用于监听客户端的连接和接......
  • 代码随想录Day4-Leetcode24-两两交换链表中的节点, 19.删除链表的倒数第N个节点, 142.环
    24.两两交换链表中的节点题目链接:https://leetcode.cn/problems/swap-nodes-in-pairs比较简单的链表题,注意使用虚拟头结点和注意变量就没问题/***Definitionfor......
  • 快速带你入门css
    css复习笔记1.css样式值1.1文字样式1p{2font-size:30px;/*设置文字大小*/3font-weight:bold;/*文字加粗*/4font-style:ital......
  • Leetcode 5.最长回文子串(区间dp)
    题目链接在这里:5.最长回文子串-力扣(LeetCode)首先肯定是个n^2的算法,枚举起点也是必要的,但是枚举终点很显然不行,但是考虑到回文串会向下兼容,因此我们可以枚举长度,这就是......