Leetcode-剑指Offer-Log-0

Posted on Nov 27, 2021

Day 1. 栈与队列 ( Easy )

剑指 Offer 09. 用两个栈实现队列

栈的特点是后进后出,队列是先进后出。使用双栈相当于使用其中一个栈作为暂存的区域,来实现队列的功能。

剑指 Offer 30. 包含min函数的栈

题目要求:实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

考虑栈后入先出的特点,解题思路是,在入栈时,每个元素添加一个额外属性:当前最小值。

Day 2. 链表 ( Easy )

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

两种思路:

辅助栈。利用栈后进先出的特性,额外开辟一个栈暂存链表数据,出栈时即为逆序。

递归。利用递归,先走到链表结尾,回溯时依次将值加入到队列中。

代码:

vector<int> reversePrint(ListNode* head){
  if(!head) return {};
  int val = head->val;
  vector<int> ret = reversePrint(head->next);
  ret.push_back(val);
  return ret;
}

粗暴法。顺序保存,用algorithm库直接reverse。

剑指 Offer 24. 反转链表

数据结构入门题目。三种基本解法。

迭代法。使用双指针。设置一个cur指针指示当前所在位置,一个pre指针指向前一个节点。

初始状态下,cur指向的是head节点,pre指向NULL。

迭代过程中,先暂存cur->next所指向的节点为temp,将cur->next指向pre,再将pre更新为cur,cur更新为temp,完成翻转。

ListNode* reverseList(ListNode* head) {
  ListNode* pre = NULL;
  ListNode* cur = head;
  while(cur){
    ListNode* next = cur->next;
    cur->next = pre;
    pre = cur;
    cur = next;
  }
  return pre;
}

辅助栈法。先遍历链表,保存value到栈中,再依次出栈,重新为链表赋值。

递归法。核心思想是,对于当前节点cur,将下一个节点cu r->next为头节点的链表进行翻转,然后将cur->next节点的next指针指向cur,完成翻转。

需要注意三个情况,(1)输入为NULL时,直接返回head(2)处理head节点时,head->next在反转后最后应该为NULL(3)最后需要返回反转后的新的头节点。

ListNode* reverseList(ListNode* head) {
    if(!head || !head->next ) return head;
    ListNode* newHead = reverseList(head->next);
    head->next->next = head;
    head->next = NULL;
    return newHead;
}

剑指 Offer 35. 复杂链表的复制

该题关键在于如何记录原链表和新链表的节点对应关系。直观解法是使用哈希,即Map。

哈希表法。空间复杂度为O(n),时间复杂度为O(n),遍历两遍原链表。

原地复制。首先将每个节点复制一遍,添加到原有节点之后,如1->2->3->NULL复制为1->1->2->2->3->3->NULL;从头开始遍历链表,将random指针指向对应节点或NULL;将链表拆分。该方法可以将空间复杂度降低为O(1)。

Day 3. 字符串 ( Easy )

剑指 Offer 05. 替换空格

使用string API操作,可以使用find、erase、insert原地操作;也可以遍历操作,新建字符串,使用append。

一定要用时间复杂度为O(n)。

可以使用双指针,用vector。

可以使用array数组,第一遍遍历确定空格数量,确定新数组的长度,第二遍遍历就可以直接复制给新数组。

从前往后遍历替换会导致元素被覆盖,则可以考虑从后往前遍历。

剑指 Offer 58 - II. 左旋转字符串

使用string API操作,则使用substr获取子串。

原地反转。思路是先反转n以前的子串,再反转n以后的子串,最后反转整个子串,可以得到一个拼接后的子串。问题化简为三个字符串反转问题。

原地字符串反转,使用双指针解法。

class Solution {
public:
    void reverseString(string &s, int i, int j){
        while(i < j){
            s[i] ^= s[j];
            s[j] ^= s[i];
            s[i] ^= s[j];
            i++;j--;
        }
    }
    string reverseLeftWords(string s, int n) {
        reverseString(s, 0, n-1);
        reverseString(s, n, s.length()-1);
        reverseString(s, 0, s.length()-1);
        return s;
    }
};

Day 4. 查找算法 ( Easy )

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

最直接思路是利用哈希表,C++中使用map。时间复杂度O(n),空间复杂度也为O(n)。

排序后遍历。先原地排序(如堆排序),若相邻元素相同,则return。

原地交换。题干中指明,所有数字都在 0~n-1 的范围内,数组索引和值是一对多的关系,因此可以通过交换值,使得数组的索引和值达到对应。即

若nums[i] = i,则继续下一个i;

若nums[i] = nums[nums[i]],说明有两个索引指向同一个值,找到该值;

否则交换nums[i]和nums[nums[i]]的值,这一步的最终必然可以将值换到与索引匹配的位置,或者找到重复值。

时间复杂度为O(n),空间复杂度为O(1)。

剑指 Offer 53 - I. 在排序数组中查找数字 I

在有序数组中查找数字出现次数,明显可以用二分查找法

可以通过二分查找找到第一个出现的数字,也可以直接使用二分查找确定左右范围。

剑指 Offer 53 - II. 0~n-1中缺失的数字

同样使用二分查找。需要注意,二分查找要点在于边界值判断、循环条件判断,并且两者需要匹配。例如,left和right值如果存在不更新,直接等于mid的情况,那么while条件需要设置为不包括left=right。

Day 5. 查找算法 (Medium)

⭐️ 剑指 Offer 04. 二维数组中的查找

一开始以为要用二分查找,但发现并不适用,并且会超时。

参考评论后,可以从矩阵右上角开始看,可以发现矩阵分布规律,类似一个二叉搜索树

因此可以设置row和col,从右上角开始,若当前val大于target,则向二叉树的左子树,即col–;若val小于target,则row++;否则返回。

需要注意可能有输入为空数组的情况。

剑指 Offer 11. 旋转数组的最小数字

简单题做法是,直接遍历查找,时间复杂度是O(n)。

进阶做法,将时间复杂度降低为O(logN)。使用二分查找

指针left、right分别指向头和尾,中间为mid。分以下情况,注意开闭区间

  • 若mid大于right,说明最小值必在(mid,right]之间,因此取left=mid+1;
  • 若mid小于right,说明最小值在[left, mid]之间,因此取right=mid;
  • 若mid和right相等,则说明(mid, right]中间的值有重复的,需要去重,但无法确定中间是否会存在更小值,如[1,1,1,1,0,1,1,],因此需要right–。

为什么不和left比较呢?因为旋转数组的特征是,右边一定比左边小。若考虑与left比较:

  • 若mid小于left,说明最小值在(left, mid]之间;

  • 若mid大于left,则说明左边是升序,但不能说明最小值位置,因为可能有[1,2,3,4,5]这类旋转长度为0的数组。

二分查找要点:二分查找有几种写法?它们的区别是什么? - Jason Li的回答 - 知乎 https://www.zhihu.com/question/36132386/answer/530313852

剑指 Offer 50. 第一个只出现一次的字符

使用哈希表,遍历两次。

Day 6. 搜索与回溯算法 (Easy)

三题BFS

剑指 Offer 32 - I. 从上到下打印二叉树

二叉树的层序遍历,利用一个队列来实现。

需要注意可能有输入为空数组的情况。

剑指 Offer 32 - II. 从上到下打印二叉树 II

层序遍历BFS。与上题区别在于按层进行打印,分别保存到不同vector中。

核心思路是,当前遍历队列时,队列中只有这一层的节点。

个人做法是,使用一个临时的队列变量保存下一层的节点,当前层的队列为空时,遍历下一个队列,代码略微繁琐。

网上做法普遍是,在while的一次循环中,再加入一个for循环,次数为当前队列长度;之后下一层节点可以直接加入到队列中(因为当前层的遍历个数已经确定)。该种做法相对更节省空间。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans;
        if(!root) return ans;
        queue<TreeNode *> points;
        points.push(root);
        while(points.size()){
            int col=points.size();
            vector<int> ans1;
            for(int i=0;i<col;i++){
                TreeNode *temp=points.front();
                points.pop();
                ans1.push_back(temp->val);
                if(temp->left) points.push(temp->left);
                if(temp->right) points.push(temp->right);
            }
            ans.push_back(ans1);
        }
        return ans;
    }
};

剑指 Offer 32 - III. 从上到下打印二叉树 III

再上一题基础上,判断每一层的层数,并进行reverse。

Day 7. 搜索与回溯算法 (Easy)

剑指 Offer 26. 树的子结构

判断二叉树B是不是二叉树A的子结构,只需要判断三种情况:B是不是A的从根节点开始的子结构;B是不是A的左子树的从根节点开始的子结构;B是不是A的右子树的从根节点开始的子结构。

判断两个二叉树是否从根节点开始重合,使用DFS。需要注意,顺序上要先判断B的节点,为NULL时,说明已经相同,

bool dfs(TreeNode* a, TreeNode*b){
    if(b==NULL) return true;
    if(a==NULL) return false;
    return a->val==b->val && dfs(a->left, b->left) && dfs(a->right, b->right);
}

bool isSubStructure(TreeNode* A, TreeNode* B) {
    if(A==NULL || B==NULL) return false;
    return dfs(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
}

剑指 Offer 27. 二叉树的镜像

递归实现。

剑指 Offer 28. 对称的二叉树

首先想法是使用上一题的镜像,在判断原始二叉树和镜像二叉树是否相同。但显然这个方法比较繁琐。

回到对称二叉树本身性质:对于二叉树中任意两个对称节点,该节点的left和right值相同,且left的left与right的right相同,left的right与right的left相同,因此可以有:

bool isSymmetricSub(TreeNode* leftNode, TreeNode* rightNode){
    if(!leftNode && !rightNode) return true;
    if((leftNode&&rightNode)==false || leftNode->val != rightNode->val) return false;
    return isSymmetricSub(leftNode->left, rightNode->right) && isSymmetricSub(leftNode->right, rightNode->left);
}

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

Day 8. 动态规划 (Easy)

剑指 Offer 10- I. 斐波那契数列

入门题。使用DP或者备忘录的递归。

剑指 Offer 10- II. 青蛙跳台阶问题

与上一题类似。

剑指 Offer 63. 股票的最大利润

动态规划:

  • 状态定义:设动态规划列表为dpdp[i]为前i天的最大利润;
  • 转移方程:前i日最大利润=max(前(i−1)日最大利润,第i日价格−前i日最低价格)
  • 初始状态:dp[0]=0
  • 返回值:dp[n-1]最后一天利益
int maxProfit(vector<int>& prices) {
    int maxp = 0;int minPrice = INT_MAX;
    for(auto iter=prices.begin();iter!=prices.end();iter++){
        minPrice = min(minPrice, *iter);
        maxp = max(maxp, *iter - minPrice);
    }
    return maxp>=0?maxp:0;
}

Day 8. 动态规划 (Medium)

动态规划方法 https://labuladong.gitee.io/algo/1/3/

⭐️ 剑指 Offer 42. 连续子数组的最大和

从暴力计算到动态规划。

暴力计算:

  • 计算每一个sum(i, j),时间复杂度为O(n^3)(i、j遍历所有,求和遍历i到j);

  • 也可以将sum(i, j)转化为sum(i, j-1)+value_j,时间复杂度为O(n^2)(i遍历所有,j遍历从i到结尾,记录最大值);

动态规划:

如果要以O(n)复杂度解决问题,那么就要在一次遍历过程中,得到以当前元素为结尾的所有数组的最大值dp[i],在此基础上,获得所有这些最大值的最大值maxValue

而在计算以A元素为起始,以B元素为结尾的数组和后,在计算以B+1元素为结尾的数组和时,只需要在前一个数组和的基础上,加上B+1元素,而前一个数组和(A起始,B结束)必然是小于dp[i]的,因此

  • dp[i] = max(nums[i], nums[i]+dp[i-1])
  • maxValue = max(maxValue, dp[i])
int maxSubArray(vector<int>& nums) {
    int maxValue = nums[0];
    int dp = nums[0];
    for(int i = 1; i < nums.size(); i++){
        dp = max(dp+nums[i], nums[i]);
        maxValue = max(dp, maxValue);
    }
    return maxValue;
}

剑指 Offer 47. 礼物的最大价值

动态规划:

将到达每格的礼物最大价值记录为dp table。到达每一个格时,当前格的最大价值=max(左边格子的最大价值, 上边格子的最大价值),因此遍历每一个格子即可,因为没有负值,因此最后取右下角格子的值。需要注意边界的行和列。时间复杂度为O(MN)。

进一步优化,可以将dp table直接保存在输入的二维数组中。空间复杂度为O(1)。

int maxValue(vector<vector<int>>& grid) {
    if(grid.size()==0) return 0;
    for(int row=0;row < grid.size(); row++){
        for(int col = 0;col < grid[0].size(); col++){
            if(row+col == 0) continue; 
            else if (row == 0) grid[row][col] += grid[row][col-1];
            else if(col == 0) grid[row][col] += grid[row-1][col];
            else grid[row][col] += max(grid[row-1][col], grid[row][col-1]);
        }
    }
    return grid[grid.size()-1][grid[0].size()-1];
}

可以进一步优化,当M、N较大时,遍历过程中的if判断语句仍然耗时,因此可以将对边界的判断移出来单独计算,两层循环从1开始。

Day 9. 动态规划 (Medium)

剑指 Offer 46. 把数字翻译成字符串

关键在于,第一位到第n位的数字的翻译方法的数量,取决于前两位是否可以被翻译,即

dp[i] = dp[i-1] + (dp[i-2:i]可以被翻译) ? 1 : 0;

而如何取出第i位的前两位,可以使用取余的方法。

这一题从左往右和从右往左处理是一样的。

剑指 Offer 48. 最长不含重复字符的子字符串

如果暴力求解,长度为N的字符串,计算所有子字符串,复杂度为O(N^2),而判断当前字符是否在子串中,复杂度为O(N),因此暴力的复杂度为O(N^3)。

动态规划:

用DP的思想求解,核心在于,要记录当前字符为结尾的最大不重复子串的值。

则可以得到状态转移方程:

  • 若当前字符s[i]前一次出现位置,不在前一个字符的最大子串范围内,则dp[j] = dp[j-1] + 1;
  • 否则,dp[j] = j - i,i为该字符上一次出现的位置。

由于要判断字符在子串中的位置,直接的思路是利用string的find_last_of函数,但复杂度高,因此可以使用哈希表记录,进一步,可以用数组替换Map,提高速度。字符的ASCII范围在0~127,因此可以用128维数组表示。

int lengthOfLongestSubstring(string s) {
    map<char, int> charMap;
    int maxLen = 0;
    int lastLen = 0;
    for(int i = 0; i < s.size(); i++){
        auto iter = charMap.find(s[i]);
        int curLen = (iter==charMap.end()) ? lastLen+1 : min(i - iter->second, lastLen+1);
        charMap[s[i]] = i;
        lastLen = curLen;
        maxLen = max(curLen, maxLen);
    }
    return maxLen;
}

动态规划的思路可以从暴力方法逐步优化得到。

滑动窗口:

这一题用滑动窗口的方法更为合适,本质思路与DP相同。DP方法记录的是子串(长度),而滑动窗口记录的是子串起始位置。

需要注意,字符左起始位置初始值的设置。

int lengthOfLongestSubstring(string s) {
    map<char, int> charMap;
    int maxLen = 0;
    int lastBeg = -1;
    for(int i = 0; i < s.size(); i++){
        auto iter = charMap.find(s[i]);
        int curBeg = (iter==charMap.end()) ? lastBeg : max(lastBeg, iter->second);
        charMap[s[i]] = i;
        lastBeg = curBeg;
        maxLen = max(maxLen, i - curBeg);
    }
    return maxLen;
}

Day 11. 双指针 (Easy)

剑指 Offer 18. 删除链表的节点

设置pre和cur双指针。

需要注意删除头节点的特例。

剑指 Offer 22. 链表中倒数第k个节点

设置快慢指针,快指针移动k-1步后,慢指针开始移动。

需要注意题目是否提及特例,如:k可能为0;k可能超过链表长度;head可能为NULL等。

Day 12. 双指针 (Easy)

剑指 Offer 25. 合并两个排序的链表

使用双指针很容易完成。每次取链表中的较小值,当其中一个链表遍历结束,直接与另一链表剩余部分拼接。

小trick在于构造一个伪头节点,可以涵盖两个链表起始位置以及出现NULL的情况。

也可以使用递归做法。

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    if(!l1) return l2;
    if(!l2) return l1;
    ListNode* ret;
    if(l1->val < l2->val){
        ret = l1;
        ret->next = mergeTwoLists(l1->next, l2);
    }else{
        ret = l2;
        ret->next = mergeTwoLists(l1, l2->next);
    }
    return ret;
}

剑指 Offer 52. 两个链表的第一个公共节点

暴力解法,使用哈希表。先遍历并记录链表A,再遍历和查询链表B的元素。时间复杂度为O(M+N),空间复杂度为O(M)。

双指针法。降低空间复杂度至O(1)。

创建两个指针,分别指向两个链表的头节点,同时开始遍历。当指针遇到链表结尾时,继续从另一个链表头开始遍历,直至两个指针相同。

分情况证明正确性:

  • 当两链表相交时,若相交前长度一致,则会在相交的地方停止遍历,若不一致,则会在遍历两链表m+n的长度后停止。
  • 若两链表不相交,则会停止在链表的尾部NULL(若相同长度则各自停止,若不同则停在另一个链表)。

可以考虑链表为空的情况。

Day 13. 双指针 (Easy)

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

**首尾双指针。**left指针向右移动,right指针向左移动,当left指向偶数切right指向奇数,则swap;

**快慢双指针。**fast用于查找要调换位置的奇数,slow指向奇数要放的位置,因为进行swap时,fast指向的是奇数,slow前的全是奇数,slow和fast之间的,全部是偶数(因为fast已经经过,所以都被调换到slow前)。

剑指 Offer 57. 和为s的两个数字

若数组为无序,可以使用hashmap的思路,记录已有的数字,每次判断是否已存在对应的另一个数字。

本题为有序数组,因此可以用双指针法。设置前后指针,两边往中间找。

剑指 Offer 58 - I. 翻转单词顺序

string reverseWords(string s) {
    string ret = "";
    int beg = 0;
    s += " ";
    for(int i=0;i<s.size();i++){
        if(s[i] == ' '){
            if(beg!=i) ret = s.substr(beg, i-beg) + " " + ret;
            beg = i+1;
        }
    }
    if(ret.size()!=0)ret.pop_back();
    return ret;
}

Day .14 搜索与回溯算法 (Medium)

剑指 Offer 12. 矩阵中的路径

矩阵搜索题,使用DFS+剪枝。

DFS的思路比较容易想,但剪枝的处理会略微复杂。在矩阵中搜索,需要考虑上、下、左、右四个方向,同时要考虑矩阵边界和已经经过的位置。

在本题中,保存经过的位置,可以使用'\0’替换走过的路径,在进入四个方向的遍历前,置为'\0',返回后恢复原值,以此节省存储空间。

此外,由于字符串的开头可能是任意一个位置,因此还需要对矩阵每个位置进行遍历。

对于M*N的矩阵,字符串长度为K,对矩阵每个位置进行K次递归,时间复杂度为$O(3^KMN) $;递归深度不超过K,空间复杂度为$O(K)$。

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        rows = board.size();
        cols = board[0].size();
        for(int i = 0; i < rows; i++) {
            for(int j = 0; j < cols; j++) {
                if(dfs(board, word, i, j, 0)) return true;
            }
        }
        return false;
    }
private:
    int rows, cols;
    bool dfs(vector<vector<char>>& board, string word, int i, int j, int k) {
        if(i >= rows || i < 0 || j >= cols || j < 0 || board[i][j] != word[k]) return false;
        if(k == word.size() - 1) return true;
        board[i][j] = '\0';
        bool res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || 
                      dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
        board[i][j] = word[k];
        return res;
    }
};

剑指 Offer 13. 机器人的运动范围

DFS+剪枝

解题思路与上一题相同。在本题中,可以只考虑向下和向右的移动。

BFS

也可以借助队列,使用BFS

class Solution {
public:
    int movingCount(int m, int n, int k) {
        vector<vector<bool>> visited(m, vector<bool>(n, 0));
        int res = 0;
        queue<vector<int>> que;
        que.push({ 0, 0, 0, 0 });
        while(que.size() > 0) {
            vector<int> x = que.front();
            que.pop();
            int i = x[0], j = x[1], si = x[2], sj = x[3];
            if(i >= m || j >= n || k < si + sj || visited[i][j]) continue;
            visited[i][j] = true;
            res++;
            que.push({ i + 1, j, (i + 1) % 10 != 0 ? si + 1 : si - 8, sj });
            que.push({ i, j + 1, si, (j + 1) % 10 != 0 ? sj + 1 : sj - 8 });
        }
        return res;
    }
};

Day 15. 搜索与回溯算法 (Medium)

剑指 Offer 34. 二叉树中和为某一值的路径

二叉树搜索,使用回溯法,思想和DFS相同:

判断边界->判断成立条件->进行下一轮遍历。

这一题需要注意的是,如何保存每一种路径,使用的方法是每进入一个新的递归,为路径path加入节点,退出时删去这个节点;若路径可用,则将路径加入到最终返回的vector中。

本题也可以使用BFS,但实现上更复杂,不推荐。

剑指 Offer 36. 二叉搜索树与双向链表

将二叉搜索树转化为一个递增的双向链表,结合二叉搜索树的性质,使用中序遍历即可,将每个遍历到的节点加入到双向链表中,最后将双向链表的尾节点和头节点相连。

class Solution {
public:
    Node* treeToDoublyList(Node* root) {
        if(root == nullptr) return nullptr;
        dfs(root);
        head->left = pre;
        pre->right = head;
        return head;
    }
private:
    Node *pre, *head;
    void dfs(Node* cur) {
        if(cur == nullptr) return;
        dfs(cur->left);
        if(pre != nullptr) pre->right = cur;
        else head = cur;
        cur->left = pre;
        pre = cur;
        dfs(cur->right);
    }
};

剑指 Offer 54. 二叉搜索树的第k大节点

二叉搜索树的特性是,左子树所有节点的值小于根节点,右子树所有节点的值大于根节点。其中序遍历(左、根、右)得到的是从小到大的递增序列。

因此可以使用中序遍历的逆序,并且判断到达k个节点后提前返回。

class Solution {
public:
    vector<int> kmax;
    void inorder_reverse(TreeNode* node, int k){
        if(kmax.size() > k || node==nullptr) return;
        inorder_reverse(node->right, k);
        kmax.emplace_back(node->val);
        inorder_reverse(node->left, k);
    }

    int kthLargest(TreeNode* root, int k) {
        inorder_reverse(root, k);
        return kmax[k-1];
    }
};

Day 16. 排序 (Easy)

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

求拼接后的最小数字,本质是排序问题。

两个数字的字符串x和y,排序规则为

  • x+y>y+x,则x > y
  • x+y<y+x,则x < y

C++中字符串的字典序排序直接用>、<符号即可。

具体排序的方法,可以选择:

1、自己实现。则本题转化为排序算法的实现。根据时间和内存限制选择最佳算法。

2、使用内置sort函数,并重载compare函数。

static bool compare(const string &a,const string &b)
{
  return a+b<b+a;
}
//////////
// or
/////////
sort(strs.begin(), strs.end(), [](string& x, string& y){ return x + y < y + x; });

快排模板

Paritition1(int A[], int low, int high) {
  int pivot = A[low];
  while (low < high) {
    while (low < high && A[high] >= pivot) {
      --high;
    }
    A[low] = A[high];
    while (low < high && A[low] <= pivot) {
      ++low;
    }
    A[high] = A[low];
  }
  A[low] = pivot;
  return low;
}

void QuickSort(int A[], int low, int high) //快排母函数
{
  if (low < high) {
    int pivot = Paritition1(A, low, high);
    QuickSort(A, low, pivot - 1);
    QuickSort(A, pivot + 1, high);
  }
}

剑指 Offer 61. 扑克牌中的顺子

自己的方法:

设置辅助数组,将五张牌记录下来,存在则置1;同时记录0的个数。

找到数组中非零的最小值,则从该数往后五个位置中,为1的数的个数+之前记录的0的个数=5,则说明为顺子。

题解方法:

五张牌为顺子的充分条件:

  • 除大小王外,所有牌无重复;(若有重复,必然不能五张为顺子)
  • 除大小王外,最大牌-最小牌<5;(若大于等于5,即使有王,也无法连续)

因此遍历一次获取最大值、最小值、是否重复即可。

Day 17. 排序 (Medium)

剑指 Offer 40. 最小的k个数

考察排序算法。

TODO: 实现多种算法

剑指 Offer 41. 数据流中的中位数