LeetCode算法题练习——题解

剑指 Offer 39. 数组中出现次数超过一半的数字

在这里插入图片描述
解题思路:本题真的特别特别简单了,首先因为数字出现次数大于一半,所以在排序后,这个数字一定超过中间的数,也就是说无论如何中间的数都一定是目标数。所以我们利用排序,然后返回排序后最中间的数字就行了。代码如下:

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        return nums[nums.size()/2];
    }
};

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

在这里插入图片描述
解题思路:本体要求我们找出和为s的两个数字,所以我们先设置两个指针,一个指向数组最左边,一个指向最右边,然后利用循环去计算最左边的数加最右边的数的值和s的值,如果比s大则证明需要将最右边的数字变小,也就是左移,同理如果小于s则证明需要扩大,也就是将最左边的值右移,直到找到和为s的两个数字,然后跳出循环。代码如下:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> res;
        int left = 0;
        int right = nums.size() - 1;
        int temp = nums[left] + nums[right];
        while(temp != target) {
            if (temp > target) {
                right--;
                temp = nums[left] + nums[right];
            } else {
                left++;
                temp = nums[left] + nums[right];
            }
        }
        res.push_back(nums[left]);
        res.push_back(nums[right]);

        return res;
    }

};

剑指 Offer 17. 打印从1到最大的n位数

在这里插入图片描述
解题思路:本题很简单,将给出的数字求出它是几位数,然后循环将数字填入数组中,直接返回就好了。代码如下:

class Solution {
public:
    vector<int> printNumbers(int n) {
        int k = pow(10,n);
        vector<int> res;
        for (int i = 1; i < k; i++) {
            res.push_back(i);
        }
        return res;
    }
};

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

在这里插入图片描述
解题思路:合并两个有序链表,也就是说将两个链表按照一定顺序合并起来,同时遍历两个链表,看谁的结点小,然后将小的结点放入链表,一直到两个链表都遍历完成,代码如下:

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == NULL) {
            return l2;
        }
        if (l2 == NULL) {
            return l1;
        }

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

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

在这里插入图片描述

解题思路:本题是一道很常规的题,删除链表的结点,首先我们先从头结点循环找到我们要删除的结点,然后利用删除函数删去结点,如果没有找到就继续遍历,代码如下:

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if (head == NULL) {
            return NULL;
        } 
        if (head->val == val) {
            return head->next;
        }
        head->next = deleteNode(head->next,val);
        return head;
    }
};

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

在这里插入图片描述
在这里插入图片描述

解题思路:
首先看到题目,让我们找到第一个公共节点,题目告诉我们了没有交点就返回null,所以我们先不考虑没有交点的情况。
看到题目后,大部分人的第一反应应该是暴力解决法(暴力解决法好像可以解决大部分问题但是大多数时候并不是最好的解决方法),本题暴力解决法就是遍历第一个链表,将它的每一个节点和另一个链表比较,但是这个的时间复杂度很高,是O(mn),所以一定是不被面试官喜欢的方法。
那么还可以有啥办法呢,我们可以看到题目中给的例子,当发现第一个公共节点时后面的链表重合了,那么假如我们可以从后向前遍历链表呢,是不是会比较优化一些,但是本题只给了我们头节点,我们用什么方法可以从后往前遍历呢?此时我想到了”栈“,根据栈的特性,后进的先出,也就是越往后越在栈顶,所以我们不妨设置两个栈,栈的大小为链表的长度,遍历两个链表,将它们的节点依次压入各自的栈,然后通过栈顶的元素进行比较,栈顶的元素相同则出栈,找到最后一个相同的节点就是第一个公共节点。这个方法的时间负责度为O(m+n),但是用到了两个辅助栈,所以他的空间复杂度一定不是最优的。
我们观察题目中给的例子可以发现有可能两个链表的长度不一样,有短有长,那么我们可以发现长的那个一个链表相对于短链表中,公共节点一定不会是比短的多出的那一部分,举个例子:比如长链表的长度为5,短链表的长度为3,那么长链表的第一、第二个节点一定不会是公共节点,换句话说就是多出来的,所以本题的第三个解法就是:先将长链表”缩减“然后遍历两个链表找到第一个相同的节点。这样的话和第二种方法相比时间复杂度都是O(m+n),但是没有栈的引用所以空间复杂度会比第二个低,具体的代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unsigned int nLengthA = GetListLength(headA);
        unsigned int nLengthB = GetListLength(headB);
        unsigned int temp;
        ListNode *pListLong = headA;
        ListNode *pListShort = headB;
        unsigned int ShortLength;
        if (nLengthA > nLengthB) {
            pListLong = headA;
            pListShort = headB;
            temp = nLengthA - nLengthB;
        } else {
            pListLong = headB;
            pListShort = headA;
            temp = nLengthB - nLengthA;
        }
        for (int i = 0; i < temp; i++) {
            pListLong = pListLong->next;

        }

        while ((pListLong != NULL)&&(pListShort != NULL)&&(pListLong!=pListShort)){
            pListLong = pListLong->next;
            pListShort = pListShort->next;
        }

        ListNode *pFirstSameNode = pListLong;

        return pFirstSameNode;

    }

    unsigned int GetListLength(ListNode *pHead) {
        unsigned int nLength = 0;
        ListNode *pNode = pHead;
        while(pNode != NULL) {
            pNode = pNode->next;
            ++nLength;
        }
        return nLength;
    }
};

剑指 Offer 57 - II. 和为s的连续正数序列

在这里插入图片描述
解题思路:

我们在看到题的时候,脑子里可能最有可能出现的思路就是暴力拆分,就想着把目标数拆成两个数或者三个数,确实是要拆成别的数的,但是我们必须细致再细致。比如有几个问题需要我们来思考:可以拆分成几种情况?序列中的数字个数是由多到少还是由少到多?这应该是我们在想到拆分之后要考虑的问题了。先说拆分成几种情况,这个和目标数有关,所以我们要控制好循环的条件。而序列中的数字个数我觉得我更偏向于由多到少,简而言之就是将最长的序列先找出来,然后依次缩短,一直到不能再缩短。大致思路有了,我们来将思路对应题目”现实化“,首先我们可以看出来需要一个序列,所谓序列就是从小到大将其中间的数字全都列出来加起来,所以我们只需要控制好序列最左边的数字 以及最右边的数字就行。所以我们定义一个最小数当作序列最左边的数字,定义一个最大数当作序列最右边的数字,然后循环结束的条件为最小数small < (target + 1) / 2,在循环里我们就可以将最小数到最大数加起来,算出一个和sum,如果sum<target,就说明我们找的序列不够长,所以就将最大数右移一位,如果sum>target,就说明我们的序列太长了,所以将序列最小数右移一位,这样就是缩短序列,相当于减去原来的最小数,如果sum等于target则说明找到一个序列,这时就把这个序列放入容器里,然后我们要找比这个序列短的序列,我们想一下序列短了,序列和却不能变,那么应该怎么办呢,将最大数右移,最小数也右移只不过移动的位数可能会不一样,这是正常的,我们只需要按正常逻辑判断就行了。将找到每一个序列放入容器,最后输出就行了。

具体代码如下:

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>> vec;
        vector<int> res;
        int small = 1;
        int big = 2;
        int sum;
        while(small < (target + 1) / 2) {
            sum = 0;
            for (int k = small; k <= big; k++) {
                sum += k;
            }
            if (sum < target) {
                    big += 1;
                } else if(sum == target) {
                    res.clear();
                    for (int i = small; i <= big; ++i) {
                        res.push_back(i);
                    }
                    vec.push_back(res);
                    big += 1;
                    
                } else {
                    small += 1;
                }
        }
    return vec;
    }
};

剑指 Offer 65. 不用加减乘除做加法

在这里插入图片描述

解题思路:
在看到本题之后,我第一反应还挺懵的,但是不能懵啊,仔细想一下这个题不就是不让用四则运算去计算加法吗,我们学计算机的都知道计算机最本质的东西就是二进制运算,之所以有了十进制也是为了用户方便,而计算机本身处理也是通过二进制的,所以这道题不就是让我们“返璞归真”吗,我们用二进制通过位运算就可以达到本题的要求。首先我们来分析一下:加法会有一个进位这么一个说法,而位运算的话我们必须知道的是一些关于位运算的基本知识,具体看这篇文章【C++】位运算

在学习完位运算之后,我们可以知道通过异或运算可以得到想加后的结果但是这个结果是不完整的,因为还牵扯到进位的问题,那我们什么时候该进位呢?1+1的时候。所以我们可以通过位与运算确定需要进位的情况,而进位的操作是左移,然后我们重复这两步直到不需要进位。
具体代码如下:

class Solution {
public:
    int add(int a, int b) {
        while(b!=0){
            int c = a&b;
            a=a^b;
            b=(unsigned)c << 1;
        }
        return a;
    }
};

剑指 Offer 62. 圆圈中最后剩下的数字

在这里插入图片描述

哎,这个题挺折磨的,我们看见圆圈这两个字想到的第一个应该就是环形,所以我们就很容易联系到环形链表(就是链表形成了一个闭环)。所以我们就先用环形链表的思想做一下这个题试试,首先我们分为以下几步:我们先创建List将元素按顺序储存在链表中,并声明一个迭代器(Iterator)将它指向第一个List的第一个元素,然后我们按条件遍历,首先我们要知道删除第m个元素,但是此时我们的迭代器指向的是第一个元素,所以到m个元素之需要移动m-1次,然后还需要注意的是判断是否指向了最后,如果指向了最后,下一步一定是将迭代器从最后移动到第一个,达成循环 。然后最后将每次找到的元素删掉,留下的最后一个元素就是我们需要的答案了,然后直接返回就好了。
代码如下:

class Solution {
public:
    int lastRemaining(int n, int m) {
        if (n < 1 || m < 1) {
            return -1;
        }

        unsigned int i = 0;

        list<int>numbers;
        for(i = 0; i < n; ++i) {
            numbers.push_back(i);
        }

        list<int>::iterator temp;
        temp = = numbers.begin();

        while(numbers.size() > 1) {
            for (int i = 1; i < m; ++i) {
                temp++;
                if (temp == numbers.end()) {
                    temp = numbers.begin();
                }
            }

            list<int>::iterator next;
            next = = ++temp;
            if (next == numbers.end()) {
                next = numbers.begin();
            }
            --temp;
            numbers.erase(temp);
            temp = next;

        }
        return *(temp);
    }
};

但是,大家要注意这个思路和方法都是对的,但是在Leetcode会:
在这里插入图片描述

但是因为这个方法我觉得是大部分人都会想到的,所以我觉得还是记录一下吧。
结下来学习一下另一个学习方法:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
还得是K神,利用动态规划解决这个问题,流程就在上面了大家一起共勉,总结一下:就是找规律,每次删除的元素在原来的顺序中都是有规律的,所以我们找到规律就行了:
代码如下:

class Solution {
public:
    int lastRemaining(int n, int m) {
        int x = 0;
        for (int i = 2; i <= n; i++) {
            x = (x + m) % i;
        }
        return x;
    }
};

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

在这里插入图片描述

解题思路:拿到本题之后,我们很容易联想到反转字符串,和这个题有异曲同工之处,首先都是反转思想,但有什么不一样呢,反转字符串是整体都反转,每一位都要反转,而本题相当于是局部反转,什么意思呢,就是反转单词的顺序,但是单词本身字符的顺序是不变的,所以也就是将后边的单词依次挪到前边,那么我们可以用什么方法呢?我推荐一种双指针方法,为什么要用双指针呢,因为我们都知道单词本身的字符顺序不能被反转,所以我们用双指针的目的就是一个指针指向某个单词的第一位,另一个指针指向单词的最后一位,两个指针就可以完完全全确定单词的位置以及长度了。那么我们的代码应该怎么写呢?我们要注意几个点,第一点就是如何去找单词并且怎样确定这个单词,第二点就是空格的问题了。首先我们利用一个指针从后往前遍历,当我们遍历到第一个不为空格的字符,就代表这是最后一个单词的最后一个字符了,然后我们将这个指针的值赋给第二个指针,让第二个指针继续向前遍历,直到遍历到了空格就代表一个单词已经遍历完了,然后我们将这个单词添加到新的字符串中,要注意每次查找后还有一个空格没添加,所以每次添加完单词还要 添加一个空格。那么问题就来了,最后一个单词添加完并且添加了空格,但是最后一个单词的后面是不需要空格的,所以我们需要将这个空格删除了。
代码如下:

class Solution {
public:
    string reverseWords(string s) {
        string res;
        int n = s.size();
        if (n == 0) {
            return res;
        }
        int right = n - 1;
        
        while(right >= 0) {
            while(right >= 0 && s[right] == ' ') {
                right--;
            }
            if (right < 0) {
                break;
            }
            int left = right;
            while(left >= 0 && s[left] != ' ') {
                left--;
            }
            res.append(s,left + 1,right - left);
            
            res += ' ';
            right = left;

        }

        if (!res.empty()) res.pop_back();
        return res;
        
    }
    
};

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

在这里插入图片描述

写了好几天算法题了,来打把扑克哈哈哈哈哈
解题思路:顺子是什么意思大家应该都知道,所以我们要判断是否为顺子,本题的解题思路就应该围绕以下几个点来看了:首先大王小王用0表示可以代表任何数字,然后顺子不能有重复的数字也就是不能有同样的牌但是除了0(0最多只有两个),所以本题我们可以先排序,排序完之后我们就遍历数组,统计一下0的个数然后找到第一个非0的数字的索引。接下来我们就可以第二次遍历了,首先判断是否有非0重读数字,这些特殊条件判断完我们就可以判断是不是顺子了,顺子无非是每一位与每一位之间相差1,所以我们先算出最后一位和第一位非零数字的值相差多少,这个数字必须小于或等于0的个数与最后一位和第一位非零数字之间的距离的和,如果大于则证明无法组成顺子,具体代码如下:

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int flag = 0, first, temp = 0;
        int x = 0;
        for(int i = 0; i < 5; i++) {
            if(nums[i] == 0) {
                flag += 1;
            } if(nums[i] != 0 && temp == 0) {
                first = i;
                temp = 1;
            }
        }
        first += 1;
        x = nums[4] - nums[first - 1];
        for (int k = first; k < 5; k++) {
            if (nums[k] == nums[k - 1]) {
                return false;
            }
            
        }
        if (x <= flag + (4 - first + 1)) {
            return true;
        } else {
            return false;
        }
    }   
      
};

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

在这里插入图片描述
解题思路:我们可以设置两个vector,然后一个里放奇数,另一个里放偶数,当我们两个vector都完成后,我们就可以把两个vector连接起来,然后返回就好了。代码如下:

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int length = nums.size();
        vector<int> a;
        vector<int> b;
        for (int i = 0; i < length; i++) {
            if (nums[i] % 2) {
                a.push_back(nums[i]);
            } else {
                b.push_back(nums[i]);
            }
        }
        a.insert(a.end(),b.begin(),b.end());
        return a;
    }
};

剑指 Offer 29. 顺时针打印矩阵

在这里插入图片描述

解题思路:
首先我看到这个题的时候还是有点没有反应过来的,因为你直接看输入和输出的结果肯定会有点迷糊,但其实我们观察矩阵发现它是二维数组二维数组其实就是一个矩阵,然后有边和宽,顺时针打印就是先从左到右,从上到下,从右到左,从下到上。然后每一次打印完,矩阵就会缩小,然后我们等到打印完最后的一个元素就将本题做完了,代码思路和这个思路差不多但是我们要注意临界条件 ,也就是何时应该跳出循环。先看从左到右,遍历完以后矩形的列就应该缩短一行;从上到下遍历完,矩形的行就应该缩短一列;从右到左遍历完,遍历完以后矩形的列就应该再缩短一行;从下到上遍历完,矩形的列就应该再缩短一列。所以我们的代码就应该可以写出来了:

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int>res;
        
        if (matrix.empty()) return res;
        int rows = matrix[0].size() - 1;
        int columns = matrix.size() - 1;    
        
        int endX = 0, endY = 0;

        while(1) {
            for (int i = endX; i <= rows; i++) {
                res.push_back(matrix[endY][i]);
            }
            if (++endY > columns) break;
            for (int i = endY; i <= columns; i++) {
                res.push_back(matrix[i][rows]);
            }
            if (--rows < endX) break;
            for (int i = rows; i >= endX; i--) {
                res.push_back(matrix[columns][i]);
            }
            if (--columns < endY) break;
            for (int i = columns; i >= endY; i--) {
                res.push_back(matrix[i][endX]);
            }
            if (++endX > rows) break;
        }
        return res;
    }
  
};

剑指 Offer 27. 二叉树的镜像

在这里插入图片描述

解题思路:首先我们先看题目是要求我们求出二叉树的镜像,什么叫镜像?镜像就是对称,所以也就是将原来的二叉树以根结点所在的直线为对称轴做出轴对称图像。我们再观察一下图可以发现每个结点的左结点和右结点的位置交换了。那么我们的代码应该咋写呢,我选择了递归交换结点的方法,将每一个结点的左右结点互换位置,代码如下:

class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
       
        TreeNode *tempNode;
        if (root != NULL) {
            tempNode = root->left;
            root->left = root->right;
            root->right = tempNode;

            mirrorTree(root->left);
            mirrorTree(root->right);
        }
       return root;
    }
    
};

剑指 Offer 28. 对称的二叉树

在这里插入图片描述

解题思路:
对称二叉树和昨天的二叉树的镜像有点相似但是又有一些区别, 首先我们要知道对称二叉树中,我们要完全对称,什么意思呢就是对应的二叉树的左结点要和右结点相等,而且只要左结点为空的右结点也要为空因为对称是左右对称,所以一定要注意是左结点与对应的右结点要相等,或者都为空,然后利用递归的思想将每一个结点都判断,这样就可以判断出是否为对称二叉树了。代码如下:

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

        if (left->val != right->val) {
            return false;
        }
        return dfs(left->right,right->left) && dfs(left->left,right->right);
    }
    
};

剑指 Offer 55 - I. 二叉树的深度

在这里插入图片描述

解题思路:二叉树的深度这道题我们在学习数据结构的二叉树时曾经学过怎么计算二叉树的深度,因为二叉树只有可能有两个分支,左子树,右子树,本题我们可以用递归的方法,去计算根结点的左子树和右子树的深度,返回深度大的那一个值,并且无论是左子树深还是右子树深,到最后返回的时候都需要加一,也就是根结点,那么根据这个思路我们就能写出代码了:

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == NULL) {
            return 0;
        }

        int left = maxDepth(root->left);
        int right = maxDepth(root->right);

        return (left > right) ? (left + 1) : (right + 1);
    }
};

剑指 Offer 55 - II. 平衡二叉树

在这里插入图片描述
解题思路:首先和昨天的二叉树的深度有一定的关系,我们要知道什么叫平衡二叉树,通过读题我们可知平衡二叉树就是二叉树中任意节点的左右子树的深度相差不超过1。所以就很简单了,通过昨天的解题思路,算出来每一个节点的左子树与右子树之差是多少,都不大于1就返回true,其余情况就返回false。代码如下:

class Solution {
public:

    int maxDepth(TreeNode* root) {
        if (root == NULL) {
            return 0;
        }

        int left = maxDepth(root->left);
        int right = maxDepth(root->right);

        return (left > right) ? (left + 1) : (right + 1);
    }


    bool isBalanced(TreeNode* root) {
        if (root == NULL) {
            return true;
        }

        int left = maxDepth(root->left);
        int right = maxDepth(root->right);
        int temp;
        if (left > right) {
            temp = left - right;

        } else {
            temp = right - left;
        }

        if (temp > 1) return false;

        return isBalanced(root->left) && isBalanced(root->right);
    }
};

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

在这里插入图片描述
解题思路:首先我们看题目中告诉我们给定的是一颗二叉搜索树,所以解题前我们要知道什么是二叉搜索树:
在这里插入图片描述

所以我们可以知道二叉搜索树就是非空左子树的所有键值都小于其根结点的值,非空右子树的所有键值大于其根结点的值,什么意思呢?我们可以把它理解为从根结点到左子树的底的左结点是递减的,而右结点是递增的,我们就能得到一个结论:左小于根小于右,所以其实本题就很简单了我们知道中序遍历是按照先左再根再右的遍历顺序遍历的,所以本题我们只需要进行中序遍历然后把每一个值放入容器中,它就会变成有序的并且是递增的,然后需要第几个我们就返回倒数第几个就行了。代码如下:

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

    int kthLargest(TreeNode* root, int k) {
        dfs(root);
        return res[res.size() - k];
    }
};

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

在这里插入图片描述

解题思路:首先我们通过读题可知本题给的二叉树是二叉搜索树,我们知道了二叉搜索树的定义后可知此题给出p,q两个值要判断出最近的结点在左子树还是右子树,然后我们递归一直按照这个条件来判断,最终找到的那个最近的公共结点,代码如下:

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

剑指 Offer 68 - II. 二叉树的最近公共祖先

在这里插入图片描述

解题思路:本题与上一道题只有一个区别那就是上一道题是二叉搜索树这一道题是二叉树,二叉树没有二叉搜索树那么有规律,所以此题不能像上一道题那样通过对比值与结点的值的大小去做,我们只能通过不断的搜素左子树和右子树判断是否在同一个结点上,还是利用递归去逐一判断。代码如下:

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == NULL) {
            return NULL;
        }
        if (root ==p || root == q) {
            return root;
        }

        TreeNode *left = lowestCommonAncestor(root->left,p,q);
        TreeNode *right = lowestCommonAncestor(root->right,p,q);

        //如果p,q分别在左右子树
        if (left != NULL && right != NULL) {
            return root;        
        } 
        //如果p,q都在左子树
        if (left != NULL) {
            return left;
        }
        if (right != NULL) {
            return right;
        }
        return NULL;
    }    
};

剑指 Offer 40. 最小的k个数

在这里插入图片描述

解题思路:本题要求我们找出最小的k个数,所以我们先利用快排给原vector进行排序,然后将前k个数放入新的vector中,然后我们返回新vector就行,代码如下:

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int> temp(k,0);
        sort(arr.begin(),arr.end());
        for (int i = 0; i < k; i++) {
            temp[i] = arr[i];
        }
        return temp;
    }
};

剑指 Offer 64. 求1+2+…+n

在这里插入图片描述
解题思路:一道因为很多限制条件把一道简单题变成了中等题,拿走了我们这么多判断我们应该怎么办啊?看着这个题型我还是特别想用递归来写,那么用递归怎么写呢?我们首先要判断什么情况下要调用递归,想一下除了常规判断我们还可以利用什么判断,其实我们可以利用短路的思想:

  • A&&B 如果A是false,则B不执行;
  • A||B 如果A为true,则B不执行;

那么就很好判断如果n的值大于0则利用递归累加,所以代码也就很简单了:

class Solution {
public:
    int sumNums(int n) {
        int temp = n;
        n>0&&(temp += sumNums(n-1));
        return temp;
    }
};

本题重点:记住短路的思想,以及短路的性质,另外还要学会利用短路做判断

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

在这里插入图片描述

解题思路:本题的解题关键在于何时买卖股票,要想利润最大肯定要在低价时买入,高价时卖出,所以本题应该设置一个最低的值当作何时买入的值,然后遍历数组当遇到比此值还小的时候就将其与最小值替换,然后当遇到比最小值大的值时就说明有利润则比较此时的利润和原利润的值,如果大于原利润则替换否则就不替换,代码如下:

class Solution {
public:
    int MAX(int a, int b) {
        if (a > b) {
            return a;
        } else {
            return b;
        }
    }
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if (n <= 1) {
           return 0;
       } 
       int temp = 0, min = prices[0];
       for (int i = 1; i < prices.size(); i++) {
           if (prices[i] <= min) {
               min = prices[i];
           } else {
               temp = MAX(temp, prices[i] - min);
           }
       }
       return temp;
    }
};

剑指 Offer 16. 数值的整数次方

在这里插入图片描述

解题思路:本题要求算出数值的整数次方,我们可以学一下快速幂的思想,具体可以看这篇:快速幂详解(超详细!!!)本题我们还有一个需要注意的点就是如果n的数值为负数怎么办,办法很简单,就是将要求的数写成倒数形式,然后再进行计算,具体代码如下:

class Solution {
public:

    double fastPow(double x, int n) {
        long double f = 1.00;
        if (n < 0) {
            x = 1 / x; 
        } 
        while(n) {
            if (n & 1 == 1) {
                f *= x;
            }     
            x *= x;
            n /= 2;         
        }
        return f;
    }
    double myPow(double x, int n) {
        if (n == 0) {
            return 1.0;
        }
        if (x == 1.0) return 1.0;

        return fastPow(x,n);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值