剑指offer刷题

目录

tips

指针的malloc问题

指针指向的是一片内存空间,如下代码

int a = 1;
int *p = &a;

那么指针p直接指向的就是a的内存地址。如果是这样:

int *p = nullptr; // 或者int *p;
*p = 1;

这样p就不会指向任何一片内存空间,所以会报错,就要使用malloc

函数参数的深拷贝问题

如果函数参数是一个指针,在函数体内改变的是指针的内容,那么在函数调用完后,指针指向的内容是改变后的内容;如果在函数体内改变的是指针自身,那么在函数调用后会失效,需要传入指针的指针

去除vector中的重复元素

先用sort函数进行排序,然后使用unique找到重复元素,unique是将迭代器中的重复元素都放到了迭代器的尾部,返回指向第一个重复元素的迭代器,之后再用erase进行擦除

int main()
{
    int myints[] = {1,2,3,1,1};
    int len = sizeof(myints)/sizeof(int);
    vector<int> vec(myints, myints + len);
    sort(vec.begin(), vec.end());
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
    for(int x : vec)
        cout << x << ",";
    return 0;
}

二维数组的查找

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

如果是一维有序数组的查找,那么用的就是二分法,对于二维数组,首先想到的是选取矩阵中间的一个数字,但是如果比查找数大,可能出现在该数字的下方或者右方,因此这里选取的是右上角的数字,如果比查找数大的话,那么就固定在该数字的左方,如果比查找数小,就固定在查找数的下方

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        int rows = array.size(), columns = array[0].size();
        int row = 0, column = columns - 1;
        while(column >= 0 && row < rows) {
            int num = array[row][column];
            if(target == num) return true;
            else if(target < num) column -= 1;
            else row += 1;
        }
        return false;
    }
};

替换空格

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

因为这道题是会加长数组而不是缩短数组,所以不能用直接用快慢指针,而是要先遍历一遍数组求空格的个数,来计算扩展后的数组长度,之后再用快慢指针倒着遍历

class Solution {
public:
	void replaceSpace(char *str,int length) {
        int space_num = 0;
        for(int i=0; i<length; i++) {
            if(str[i] == ' ') space_num++;
        }
        int new_length = length + space_num*2;
        int slow, fast = new_length - 1;
        for(slow = length-1; slow>=0; slow--) {
            if(str[slow] != ' ') str[fast--] = str[slow];
            else {
                str[fast--] = '0';
                str[fast--] = '2';
                str[fast--] = '%';
            }
        }
	}
};

从尾到头打印链表

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

从头到尾遍历链表,存入vector中,然后倒序输出

class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> temp;
        vector<int> res;
        ListNode *p = head;
        while(p != nullptr) {
            temp.push_back(p->val);
            p = p->next;
        }
        for(int i=temp.size()-1; i>=0; i--) {
            res.push_back(temp[i]);
        }
        return res;
    }
};

重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

递归

class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        int size = pre.size();
        if(size == 0) return nullptr;
        int val = pre[0], left_num = 0;
        vector<int> left_pre, right_pre, left_vin, right_vin;
        TreeNode *root = new TreeNode(val);
        while(left_num<size && vin[left_num] != val) {
            left_vin.push_back(vin[left_num]);
            left_pre.push_back(pre[left_num+1]);
            left_num++;
        }
        left_num++;
        while(left_num < size) {
            right_vin.push_back(vin[left_num]);
            right_pre.push_back(pre[left_num++]);
        }
        root->left = reConstructBinaryTree(left_pre, left_vin);
        root->right = reConstructBinaryTree(right_pre, right_vin);
        return root;
    }

};

用两个栈实现队列

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

这道题的输入形式是“push1, push2, pop, push3"这样

class Solution
{
public:
    void push(int node) {
        stack1.push(node);
    }

    int pop() {
        while(stack1.size() > 0) {
            int temp = stack1.top();
            stack1.pop();
            stack2.push(temp);
        }
        int res = stack2.top();
        stack2.pop();
        if(stack2.size() > 0) {	// 要把剩下的元素都再退回到stack1中
            int temp = stack2.top();
            stack2.pop();
            stack1.push(temp);
        }
        return res;
    }

private:
    stack<int> stack1;
    stack<int> stack2;
};

旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组[3,4,5,1,2]为[1,2,3,4,5]的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

数组查找数字使用的都是二分法,如果mid大于最左边,说明左边是有序的,如果mid小于最右边,说明右边是有序的,但是查找最小值和查找具体的某个值不同的是,循环退出的条件不一样,还有就是当左右边界发生变化时,要把mid包含进去。

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        int left = 0, right = rotateArray.size() - 1, mid;
        while(left < right) {
            if(left == right - 1) return rotateArray[right];
            int mid = (left + right) / 2;
            if(rotateArray[mid] >= rotateArray[left]) left = mid;
            else if(rotateArray[mid] <= rotateArray[right]) right = mid;
        }
        return 0;
    }
};

跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

f(n)用来表示跳上n个台阶总共有多少种跳法,如果第一次选择跳上1级,那么后面的就有f(n-1)种跳法,如果第一次选择跳2级,后面就有f(n-2)种跳法,所以f(n) = f(n-1) + f(n-2)

class Solution {
public:
    int jumpFloor(int number) {
        if(number == 1) return 1;
        if(number == 2) return 2;
        return jumpFloor(number-1) + jumpFloor(number-2);
    }
};

变态跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

用动态规划

class Solution {
public:
    // 动态规划的递归方程是f(n) = f(n-1) + f(n-2) + …… + f(1) + 1
    int jumpFloorII(int number) {
        int sum = 1;
        int first = 1;
        while(number > 1) {
            first = sum;	
            sum += first;
            number--;
        }
        return sum;
    }
};

矩形覆盖

我们可以用2x1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2x1的小矩形无重叠地覆盖一个2xn的大矩形,总共有多少种方法?
比如n=3时,2x3的矩形块有3种覆盖方法
在这里插入图片描述

用递归,和青蛙跳台阶是一样的解法,f(n)=f(n-1)+f(n-2),也就是竖着放置第一块矩形,或者横着放置两块矩形

class Solution {
public:
    int rectCover(int number) {
        if(number <= 2) return number;
        return rectCover(number - 1) + rectCover(number - 2);
    }
};

二进制中1的个数

输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。

n-1与n做位与操作,会将n的最右边的1变为0,其他位不变,因此n有多少个1,就循环多少次

class Solution {
public:
     int  NumberOf1(int n) {
         int count = 0;
         while(n) {
             n = (n-1) & n;
             count++;
         }
         return count;
     }
};

数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0。

求整数次方,使用的是递归,在这里插入图片描述
同时需要考虑的是base=0, exponent=0的情况

class Solution {
public:
    double Power(double base, int exponent) {
        double res;
        if(exponent == 0) return 1;
        if(exponent < 0) {
            if(base == 0) return 0;
            res = PowerUnsignedExponent(base, -exponent);
        }
        else {
            res = PowerUnsignedExponent(base, exponent);
        }
        return exponent<0? 1/res: res;
    }
    double PowerUnsignedExponent(double base, int exponent) {
        if(exponent == 0) return 1;
        if(exponent == 1) return base;
        double res = PowerUnsignedExponent(base, exponent/2);
        res *= res;
        if(exponent%2 == 1) {
            res *= base;
        }
        return res;
    }
};

调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

要保证数字的相对顺序保持不变,就只能用空间换时间的方式

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        int sum = 0, ji = 0, ou = 0;
        vector<int> res(array.size());
        for(int i=0; i<array.size(); i++) {
             if(array[i]%2 == 1) {
                 sum++;
             }
        }
        for(int i=0; i<array.size(); i++) {
            if(array[i]%2 == 1) {
                res[ji++] = array[i];
            }
            else {
                res[sum+ou] = array[i];
                ou++;
            }
        }
        for(int i=0; i<res.size(); i++) {
            array[i] = res[i];
        }
    }
};

如果不需要保证数字的相对位置不变,就在原数组基础上就可以,用两个指针,一个从前遍历,一个从后,从前遍历的偶数与从后遍历的奇数交换

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        int p1 = 0, p2 = array.size() - 1;
        while(p1 < p2) {
            while(p1 < p2 && array[p1] & 1 == 1) {
                p1++;
            }
            while(p1 < p2 && array[p2] & 1 == 0) {
                p2--;
            }
            if(p1 < p2) {
                int temp = array[p1];
                array[p1] = array[p2];
                array[p2] = temp;
            }
        }
    }
};

链表中倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点
输入:1,{1,2,3,4,5}
输出:5

是单向链表并且链表只能遍历一次,因此定义两个指针,第一个指针从链表的头开始遍历k-1步,第二个指针不动,然后两个指针一起开始遍历,当第一个指针遍历到链表的尾部时,第二个指针指向链表中倒数第k个结点

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead == nullptr || k == 0) return 0;
        ListNode *p = pListHead, *q = pListHead;
        for(int i=0; i < k-1; i++) {
            p = p->next;
        }
        if(p == nullptr) return nullptr;	// 说明k大于链表的结点个数
        while(p->next != nullptr) {
            p = p->next;
            q = q->next;
        }
        return q;
    }
};

反转链表

输入一个链表,反转链表后,输出新链表的表头。

没有用不断申请新结点的做法,是直接在原有的链表上操作,将每一个结点的指针指向反向,pnext用来记录原来链表的下一个结点,r是新链表中的上一个结点,p就是用来遍历原来的链表

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode *pnext = nullptr, *r = nullptr, *p = pHead;
        if(pHead == nullptr) return nullptr;
        while(p != nullptr) {
            pnext = p->next;
            p->next = r;
            r = p;
            p = pnext;
        }
        return r;
    }
};

合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

不使用申请新空间的方式,在原有的链表上进行操作,要注意的是头结点的处理,可以设置一个虚的头结点,然后直接p->next就可以

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode *head = new ListNode(0);	// 设置一个虚的头结点
        ListNode *p = head;
        if(pHead1 == nullptr && pHead2 == nullptr) return nullptr;  
        while(pHead1 && pHead2) {
            if(pHead1->val < pHead2->val) {
                p->next = pHead1;
                p = p->next;
                pHead1 = pHead1->next;
            }
            else {
                p->next = pHead2;
                p = p->next;
                pHead2 = pHead2->next;
            }
        }
        if(pHead1) p->next = pHead1;
        if(pHead2) p->next = pHead2;
        return head->next;
    }
};

树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

用递归

class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        bool res = false;
        if(pRoot1 != nullptr && pRoot2 != nullptr) {
            if(pRoot1->val == pRoot2->val) {
                res = EqualTree(pRoot1, pRoot2);
            }
            if(!res) {
                res = HasSubtree(pRoot1->left, pRoot2);
            }
            if(!res) {
                res = HasSubtree(pRoot1->right, pRoot2);
            }
        }
        return res;
    }
    bool EqualTree(TreeNode* pRoot1, TreeNode* pRoot2) {
        if(pRoot2 == nullptr) return true;	// 如果pRoot2为空 说明pRoot2遍历完成
        if(pRoot1 == nullptr) return false;
        if(pRoot1->val == pRoot2->val) {
            return (EqualTree(pRoot1->left, pRoot2->left) && EqualTree(pRoot1->right, pRoot2->right));
        }
        return false;
    }
};

二叉树的镜像

操作给定的二叉树,将其变换为二叉树的镜像
在这里插入图片描述

class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot == nullptr) return;
        if(pRoot->left == nullptr && pRoot->right == nullptr) return;
        TreeNode *temp = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = temp;
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }
};

顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

就按照从左往右,从上往下,从右往左,从下往上的顺序依次遍历,注意调整边界,并且要及时判断退出条件

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        int left = 0, right = matrix[0].size() - 1, up = 0, down = matrix.size() - 1;
        vector<int> res;
        while(up <= down && left <= right) {
            for(int j=left; j<=right; j++) {
                res.push_back(matrix[up][j]);
            }
            up++;
            if(up > down) break;
            for(int i=up; i<=down; i++) {
                res.push_back(matrix[i][right]);
            }
            right--;
            if(left > right) break;
            for(int j=right; j>=left; j--) {
                res.push_back(matrix[down][j]);
            }
            down--;
            if(up > down) break;
            for(int i=down; i>=up; i--) {
                res.push_back(matrix[i][left]);
            }
            left++;
            if(left > right) break;
        }
        return res;
    }
};

包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

用两个栈实现,一个是普通的栈,一个栈只用来装当前的最小元素,当出栈时,两个栈同时出栈
在这里插入图片描述

class Solution {
public:
    stack<int> s1,s2;
    void push(int value) {
        s1.push(value);
        if(s2.empty() || s2.top() > value) {
            s2.push(value);
        } else{
            s2.push(s2.top());
        }
    }
    void pop() {
        if(s1.size()>0 && s2.size()>0) {
            s1.pop();
            s2.pop();
        }
    }
    int top() {
        return s2.top();
    }
    int min() {
        return top();
    }
};

栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

依次入栈,如果栈顶元素是弹出序列,则出栈,弹出序列往后遍历一个,如果不是弹出序列的第一个,则继续入栈,最后如果没有元素可以继续入栈,并且栈顶元素也不是弹出序列的第一个,则说明不是弹出序列

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        if(pushV.size() == 0) return true;
        stack<int> s;
        int p_pop = 0, p_push = 1;
        s.push(pushV[0]);
        while(!s.empty()){
            if(s.top() == popV[p_pop]) {
                s.pop();
                p_pop++;
            } else {
                if(p_push == pushV.size()) break;
                s.push(pushV[p_push++]);
            }
        }
        if(!s.empty()) {
            return false;
        }
        return true;
    }
};

从上往下打印二叉树

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

层次遍历

class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        if(root == nullptr) return {};
        queue<TreeNode*> que;
        vector<int> res;
        que.push(root);
        while(!que.empty()) {
            TreeNode *p = que.front();
            que.pop();
            res.push_back(p->val);
            if(p->left) que.push(p->left);
            if(p->right) que.push(p->right);
        }
        return res;
    }
};

🌸 二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。

后序遍历的最后一个节点是根节点,前面的节点比根节点小的就在左子树,比根节点大的就在右子树,如果出现在右子树的节点比根节点小了,那么就false

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.size() == 0) return false;
        vector<int> left, right;
        int i, j;
        int root = sequence.back();
        for(i=0; i<sequence.size()-1; i++) {
            if(sequence[i] < root) {
                left.push_back(sequence[i]);
            } else {
                break;
            }
        }
        for(j=i; j<sequence.size()-1; j++) {
            if(sequence[j] > root) {
                right.push_back(sequence[j]);
            } else {
                return false;
            }
        }
        bool left_flag = true, right_flag = true;    // 这个地方要注意一下 因为如果一边是空的传进去了 会判断是false
        if(!left.empty()) left_flag = VerifySquenceOfBST(left);
        if(!right.empty()) right_flag = VerifySquenceOfBST(right);
        return left_flag && right_flag;
    }
};

🌸二叉树中和为某一值的路径(DFS)

输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

老DFS了

class Solution {
public:
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        vector<vector<int>> res;
        vector<int> path;
        DFS(root, 0, expectNumber, path, res);
        return res;
    }
    void DFS(TreeNode* root, int currentSum, int expectSum, vector<int>& path, vector<vector<int>>& paths) {
        if(root == nullptr) return;
        currentSum += root->val;
        path.push_back(root->val);
        if(root->left == nullptr && root->right == nullptr) {
            if(currentSum == expectSum) {
                paths.push_back(path);
            }
        }
        if(root->left) DFS(root->left, currentSum, expectSum, path, paths);
        if(root->right) DFS(root->right, currentSum, expectSum, path, paths);
        path.pop_back();
    }
};

🌸复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

分成三步
将每个节点后面插入clone节点
clone节点的random指针
将两个链表拆分开

class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(pHead == nullptr) return nullptr;
        RandomListNode *p = pHead;
        while(p) {
            RandomListNode *pClone = new RandomListNode(p->label);
            pClone->next = p->next;
            pClone->random = nullptr;
            p->next = pClone;
            p = pClone->next;
        }
        p = pHead;
        while(p) {
            RandomListNode *temp = p->next;
            if(p->random != nullptr) {
                temp->random = p->random->next;
            }
            p = p->next->next;
        }
        p = pHead;
        RandomListNode *qHead = p->next, *q = qHead;
        while(p) {
            p->next = q->next;
            p = p->next;
            if(p) {
                q->next = p->next;
                q = q->next;
            }
        }
        return qHead;
    }
};

🌸二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

用中序遍历,要记录下遍历时的前一个节点
要注意的就是preNode传进去的是指针的指针,如果只传进去指针,那么在函数内不改变的是指针指向的值,如果在函数内改变指针,那么在函数外是没有用的,传进去的是指针的指针,就可以改变指针自身

class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(pRootOfTree == nullptr) return nullptr;
        TreeNode* preNode = nullptr;
        LinkConvert(pRootOfTree, &preNode);
        TreeNode* p = pRootOfTree;
        while(p && p->left) {
            p = p->left;
        }
        return p;
    }
    void LinkConvert(TreeNode* pRoot, TreeNode** preNode) {
        if(pRoot == nullptr) return;
        if(pRoot->left) LinkConvert(pRoot->left, preNode);
        if(*preNode != nullptr) {
            (*preNode)->right = pRoot;
        }
        pRoot->left = *preNode;
        *preNode = pRoot;
        if(pRoot->right) LinkConvert(pRoot->right, preNode);
    }
};

🌸字符串的排列(全排列问题 DFS)

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

用递归进行全排列,重点是要在排列之后进行去重

class Solution {
public:
    vector<string> Permutation(string str) {
        if(str == "") return {};
        vector<string> res;
        Permu(str, 0, res);
        sort(res.begin(), res.end());
        auto it = unique(res.begin(), res.end());
        res.erase(it, res.end());
        return res;
    }
    void Permu(string& str, int begin, vector<string>& res) {
        if(begin == str.length()) res.push_back(str);
        for(int i=begin; i<str.length(); i++) {
            swap(str[begin], str[i]);
            Permu(str, begin+1, res);
            swap(str[begin], str[i]);
        }
    }
};

🌸数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

因为超过数组长度的一半,如果是排好序的数字,那么就是数组中第n/2大的数字,所以题目就转化为寻找数组中第k大的数字,用的是快排,只不过不用全部排完

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        int length = numbers.size();
        if(length == 0) return 0;
        if(length == 1) return numbers[0];
        int k = length >> 1, left = 0, right = length - 1;
        int index = Partition(numbers, left, right);
        while(k != index) {
            if(k > index) {
                left = index + 1;
                index = Partition(numbers, left, right);
            } else {
                right = index - 1;
                index = Partition(numbers, left, right);
            }
        }
        int result = numbers[index], time = 0;
        for(int i=0; i<length-1; i++) {
            if(numbers[i] == result) {
                time++;
            }
        }
        if(time <= length/2) {
            return 0;
        }
        return result;
    }
    int Partition(vector<int>& a, int i, int j) {    // 注意这个传进去的是引用
        int temp = a[i];
        while(i < j) {
            while(temp >= a[j] && i < j){
                j--;
            }
            a[i] = a[j];
            while(temp <= a[i] && i < j) {
                i++;
            }
            a[j] = a[i];
        }
        a[j] = temp;
        return j;
    }
};

最小的k个数

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。

用快排找最小的第k个数,然后第k个数之前的数就是最小的k个数

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if(k > input.size() || input.size() == 0 || k == 0) return {};
        int start = 0, end = input.size() - 1;
        int index = Partition(input, start, end);
        vector<int> res;
        while((k-1) != index) {
            if((k-1) > index) {
                start = index + 1;
                index = Partition(input, start, end);
            }
            else {
                end = index - 1;
                index = Partition(input, start, end);
            }
        }
        for(int i=0; i<=index; i++) {
            res.push_back(input[i]);
        }
        sort(res.begin(), res.end());
        return res;
    }
    int Partition(vector<int>& a, int i, int j) {
        int temp = a[i];
        while(i < j) {
            while(temp <= a[j] && i < j) {
                j--;
            }
            a[i] = a[j];
            while(temp >= a[i] && i < j) {
                i++;
            }
            a[j] = a[i];
        }
        a[j] = temp;
        return j;
    }
};

上面的做法有限制,因为要修改输入的数组,如果不能修改输入的数组,那么就需要O(nlogk)的时间复杂度,就是用大顶堆来实现。
c++实现大小堆用的是priority_queue

std::priority_queue<int> big_heap; //默认构造是最大堆
std::priority_queue<int, std::vector<int>, //最小堆构造法
					std::greater<int> > small_heap;
std::priority_queue<int, std::vector<int>, //最大堆构造法
					std::less<int> > big_heap2;


class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if(input.size() == 0 || k == 0 || input.size() < k) return{};
        priority_queue<int, vector<int>, less<int> > q;	// 这里一定要将两个>分开
        vector<int> res;
        for(int i=0; i<input.size(); i++) {
            if(q.size() < k) {
                q.push(input[i]);
            }
            else if(q.top() > input[i]){
                q.pop();
                q.push(input[i]);
            }
        }
        for(int i=0; i<k; i++) {
            res.push_back(q.top());
            q.pop();
        }
        return res;
    }

};

连续子数组的最大和

求一个数组的连续子向量的最大和

动态规划

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        int sum = 0, max = INT_MIN;    // sum记录的是包含每一个值的最大值
        for(int i=0; i<array.size(); i++) {
            if(sum > 0) {
                sum += array[i];
            } else {
                sum = array[i];
            }
            if(sum > max) {
                max = sum;
            }
        }
        return max;
    }
};

整数中1出现的次数(从1到n整数)

https://blog.csdn.net/tangyuan_sibal/article/details/88829203
规律就是,判断当前位
当前位=0,当前位上1的个数为高位上的数字当前位
当前位=1,当前位上1的个数为高位上1的数字
当前位+(低位+1)
当前位>1,当前位上1的个数为(高位上的数字+1)*当前位

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
        int count = 0;
        int pos = 1;
        while(n / pos) {
            int current = (n/pos)%10;
            int before = n/(pos*10);
            int after = n-(n/pos)*pos;
            if(current == 0) count += before*pos;
            else if(current == 1) count += (before*pos + after + 1);
            else count += (before+1)*pos;
            pos *= 10;
        }
        return count;
    }
};

把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

将数组进行排序,重新定义排序的规则

class Solution {
public:
    string PrintMinNumber(vector<int> numbers) {
        if(numbers.size() < 0) return "";
        sort(numbers.begin(), numbers.end(), cmp);
        string res = "";
        for(int i=0; i<numbers.size(); i++) {
            res += to_string(numbers[i]);
        }
        return res;
    }
    static bool cmp(int a, int b) {
        string stra = to_string(a) + to_string(b);
        string strb = to_string(b) + to_string(a);
        return stra < strb;
    }
};

丑数

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

每一个丑数都是由前面的数*2,*3,5得到的,所以将前面的每一个丑数都用数组存起来。那么如何找到下一个丑数呢,其实就是比较前面的某三个数分别2,*3,*5的大小,所以要记录每次乘的那几个数的位置

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if(index <= 0) return 0;
        vector<int> UglyNumbers;
        UglyNumbers.push_back(1);
        int p2 = 0, p3 = 0, p5 = 0, nextIndex = 1;
        while(nextIndex < index) {
            UglyNumbers.push_back(min3(UglyNumbers[p2]*2, UglyNumbers[p3]*3, UglyNumbers[p5]*5));
            while(UglyNumbers[p2]*2 <= UglyNumbers[nextIndex]) p2++;
            while(UglyNumbers[p3]*3 <= UglyNumbers[nextIndex]) p3++;
            while(UglyNumbers[p5]*5 <= UglyNumbers[nextIndex]) p5++;
            nextIndex++;
        }
        return UglyNumbers[nextIndex-1];
    }
    int min3(int n1, int n2, int n3) {
        int min = n1<n2? n1: n2;
        return min<n3? min: n3;
    }

};

第一个只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).(从0开始计数)

用map来记录每个字符以及每个字符出现的次数,之后再遍历一遍字符串

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        map<char, int> m;
        for(int i=0; i<str.length(); i++) {
            if(m.count(str[i]) == 0) {
                m[str[i]] = 1;
            } else{
                m[str[i]]++;
            }
        }
        for(int i=0; i<str.length(); i++) {
            if(m[str[i]] == 1) {
                return i;
            }
        }
        return -1;
    }
};

数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

分治算法,并且要边排序边计算逆序对个数

class Solution {
public:
    int InversePairs(vector<int> data) {
        if(data.size() < 2) return 0;
        int length = data.size();
        long long res = solve(data, 0, length - 1);
        return res%1000000007;
    }
    long long solve(vector<int>& data, int start, int end) {
        if(start >= end) return 0;
        int mid = (start + end) / 2;
        long long left = solve(data, start, mid);
        long long right = solve(data, mid+1, end);
        long long left_right = merge(data, start, mid, end);
        return left + right + left_right;
    }
    long long merge(vector<int>& data, int start, int mid, int end) {
        vector<int> temp;
        long long count = 0;
        int i = start, j = mid+1;
        while(i <= mid && j <= end) {
            if(data[i] > data[j]) {
                count += mid-i+1;
                temp.push_back(data[j++]);
            } else{
                temp.push_back(data[i++]);
            }
        }
        while(i <= mid) temp.push_back(data[i++]);
        while(j <= end) temp.push_back(data[j++]);
        int k = 0, m = 0;
        for(k=start; k<=end; k++) {
            data[k] = temp[m++];
        }
        return count;
    }
};

两个链表的第一个公共结点

输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

先分别计算两个链表的长度,然后长度长的那个链表先走几步,然后两个链表一起走

class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        if(pHead1 == nullptr || pHead2 == nullptr) return nullptr;
        int length1 = 1, length2 = 1, dis;
        ListNode *p1 = pHead1, *p2 = pHead2;
        while(p1) {
            p1 = p1->next;
            length1++;
        }
        while(p2) {
            p2 = p2->next;
            length2++;
        }
        if(length1 > length2) {
            dis = length1 - length2;
            p1 = pHead1;
            while(dis) {
                p1 = p1->next;
                dis--;
            }
            p2 = pHead2;
            
        }
        else {
            dis = length2 - length1;
            p2 = pHead2;
            while(dis) {
                p2 = p2->next;
                dis--;
            }
            p1 = pHead1;
        }
        while(p1 != p2) {
            p1 = p1->next;
            p2 = p2->next;
        }
        return p1;
           
    }
};

数字在升序数组中出现的次数

统计一个数字在升序数组中出现的次数。

用二分法对第一次出现k和最后一次出现k的位置进行查找

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        if(data.size() <= 0) return 0;
        int first = getFirstK(data, k);
        int last = getLastK(data, k);
        if(first == -1 || last == -1) return 0;
        return last - first + 1;
    }
    int getFirstK(vector<int> data, int k) {
        int left = 0, right = data.size() - 1, mid;
        while(left <= right) {
            mid = (left + right) / 2;
            if(data[mid] > k) right = mid - 1;
            else if(data[mid] < k) left = mid + 1;
            else {
                if(mid > 0 && data[mid-1] == k) right = mid - 1;
                else return mid;
            }
        }
        return -1;
    }
    int getLastK(vector<int> data, int k) {
        int left = 0, right = data.size() - 1, mid;
        while(left <= right) {
            mid = (left + right) / 2;
            if(data[mid] > k) right = mid - 1;
            else if(data[mid] < k) left = mid + 1;
            else {
                if(mid < (data.size() - 1) && data[mid+1] == k) left = mid + 1;
                else return mid;
            }
        }
        return -1;
    }
};

二叉树的深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

递归

class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
        if(pRoot == nullptr) return 0;
        int left = TreeDepth(pRoot->left) + 1;
        int right = TreeDepth(pRoot->right) + 1;
        return left>right?left:right;
    }
};

平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树

如果只是用递归来计算高度,然后每个节点进行比较,那么会重复计算,所以将每个节点的高度记录下来,然后用后序遍历,因为后序遍历先遍历子树,这样就可以避免重复计算

class Solution {
public:
    bool IsBalanced_Solution(TreeNode* pRoot) {
        if(pRoot == nullptr) return true;
        int dep = 0;
        return Depth(pRoot, dep);
        
    }
    bool Depth(TreeNode* pRoot, int& dep) {
        if(pRoot == nullptr) return true;
        int left = 0, right = 0;
        if(Depth(pRoot->left, left) && Depth(pRoot->right, right)) {
            if(left - right == -1 || left - right == 1 || left - right == 0) {
                dep  = max(left, right) + 1;
                return true;
            }
        }
        return false;
    }
};

🌸数组中只出现一次的数字

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

先考虑数组中只有一个数字出现了一次,那么就把所有的数字都异或起来,相同的数字异或是0,最后得到的数字就是只出现了一次的数字。再考虑数组中有两个数字出现了一次,就要将数组分成两部分,每一部分的数组分别进行异或。将所有的数字异或,得到的结果是这两个数字异或的结果,该结果中至少有一位为1,将最低的一位1找出,然后按照该位是否为1划分数组,相同的数字一定会被分到一组。

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        if(data.size() < 2) {
            num1 = nullptr;
            num2 = nullptr;
        }
        int yihuo = 0;
        for(int i=0; i<data.size(); i++) {
            yihuo ^= data[i];
        }
        int bit = 0;
        while((yihuo & 1) == 0) {
            yihuo = yihuo >> 1;
            bit++;
        }
        *num1 = *num2 = 0;
        for(int i=0; i<data.size(); i++) {
            if(isBit(data[i], bit)) {
                *num1 ^= data[i];
            } else {
                *num2 ^= data[i];
            }
        }
    }
    bool isBit(int number, int bit) {
        number = number >> bit;
        return number&1;
    }
};

和为S的连续正数序列

找出所有和为S的连续正数序列

设置两个指针,分别指向第一个数和第二个数,然后计算累加和,如果当前和大于目标,就舍弃第一个指针,如果当前和小于目标,就增加第二个指针

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        if(sum <= 2) return {};
        int left = 1, right = 2, cursum = left + right;
        vector<vector<int> > res;
        while(left < right && right <= sum) {
            if(cursum == sum) {
                vector<int> temp;
                for(int i=left; i<=right; i++) {
                    temp.push_back(i);
                }
                res.push_back(temp);
                right++;
                cursum += right;
            } else if(cursum < sum) {
                right++;
                cursum += right;
            } else {
                cursum -= left;
                left++;
            }
        }
        return res;
    }
};

和为S的两个数

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

设置两个指针

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        if(array.size() < 2) return {};
        int left = 0, right = array.size() - 1;
        while(left < right) {
            if((array[left] + array[right]) == sum) {
                return {array[left], array[right]};
            } else if((array[left] + array[right]) < sum) {
                left++;
            } else right--;
        }
        return {};
    }
};

左旋转字符串

将字符串循环左移k位

用自带的reverse函数

class Solution {
public:
    string LeftRotateString(string str, int n) {
        if(str.length() == 0) return str;
        n = n % str.length();
        reverse(&str[0], &str[n]);
        reverse(&str[n], &str[str.length()]);
        reverse(&str[0], &str[str.length()]);
        return str;
    }
};

翻转单词顺序列

“student. a am I” -> “I am a student.”

class Solution {
public:
    string ReverseSentence(string str) {
        if(str.length() == 0) return str;
        stack<string> s;
        string word = "";
        for(int i=0; i<str.length(); i++) {
            if(str[i] == ' ') {
                //word += '\0';
                s.push(word);
                word = "";
            }
            else {
                word += str[i];
            }
        }
        s.push(word);
        string res = "";
        while(!s.empty()) {
            res += s.top();
            res += ' ';
            s.pop();
        }
        res[res.length() - 1] = '\0';
        return res;
    }
};

扑克牌顺子

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的,2~10为数字本身,A为1,J为11, Q为12, K为13,而大小王可以看成是任意数字。

先对数组进行排序,然后计算0的个数,然后再看数组中数字有哪些空缺,要注意不能有多个重复数字

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        if(numbers.size() <= 0) return false;
        sort(numbers.begin(), numbers.end());
        int zero = 0, gap = 0;
        for(int i=0; i<numbers.size(); i++) {
            if(numbers[i] == 0) zero++;
            else {
                if(i > 0 && numbers[i] == numbers[i-1]) return false;
                if(i > 0 && numbers[i-1] != 0 && numbers[i] != (numbers[i-1] + 1)) {
                    gap += (numbers[i] - numbers[i-1] - 1);
                }
            }
        }
        if(zero == gap) return true;
        if(gap == 0) return true;
        return false;
    }
};

孩子们的游戏(圆圈中剩下的最后一个数)

0,1,……,n-1这n个数字排成一个圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字

用循环链表

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if(n == 0 || m == 0) return -1;
        ListNode *head = new ListNode(0);
        ListNode *p = head;
        for(int i=1; i<n; i++) {
            ListNode *temp  = new ListNode(i);
            p->next = temp;
            p = p->next;
        }
        p->next = head;
        p = head;
        while(p->next != p) {
            for(int i=1; i<m-1; i++) {
                p = p->next;    // 此时的p是要出列的上一个结点
            }
            p->next = p->next->next;
            p = p->next;
        }
        return p->val;
    }
};

求1+2+……+n

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

class Solution {
public:
    int Sum_Solution(int n) {
        int res = n;
        res && (res += Sum_Solution(n-1));
        return res;
    }
};

不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

用二进制加法。先对两个数进行不进位相加,就用异或操作代替,然后计算进位(两个数进行位与然后左移一位),之后再对进位和不进位的和进行相加。

class Solution {
public:
    int Add(int num1, int num2)
    {
        int sum, res;
        while(num2) {
            sum = num1^num2;
            res = (num1&num2)<<1;
            num1 = sum;
            num2 = res;
        }
        return num1;
    }
};

把字符串转换成整数

将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

题目注意的就是要考虑非法输入的情况,要注意有非法字符;只有一个正负号;超出了整型范围

class Solution {
public:
    int StrToInt(string str) {
        int flag = 0;
        long long res = 0;
        if(str.length() == 1 && (str[0] == '+' || str[0] == '-')) return 0;
        for(int i=0; i<str.length(); i++) {
            if(i == 0 && str[i] == '-') {flag = -1; continue;}
            else if(i == 0 && str[i] == '+') {continue;}
            else if(!(str[i] >= '0' && str[i] <= '9')) return 0;
            res = res * 10 + str[i] - '0';
            if(flag == 0 && res > INT_MAX) return 0;
            if(flag == -1 && res < INT_MIN) return 0;
        }
        int res_int = (flag==-1?-res:res);
        return res_int;
    }
};

数组中重复的数字

请找出数组中第一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
返回描述:
如果数组中有重复的数字,函数返回true,否则返回false。
如果数组中有重复的数字,把重复的数字放到参数duplication[0]中。(ps:duplication已经初始化,可以直接赋值使用。)

第一种方法就是用map将数字和每个数字出现的次数存起来。第二种不用额外存储空间的办法就是将遍历到的每一个数字都存储到value对应的下标上,然后如果有重复的数字,就去看那个value对应的下标是不是存了该值,如果存了,那么这个数字就是重复的数字

class Solution {
public:
    bool duplicate(int numbers[], int length, int* duplication) {
        /*map<int, int> m;
        for(int i=0; i<length; i++) {
            if(!m.count(numbers[i])) {
                m[numbers[i]] = 1;
            } else {
                duplication[0] = numbers[i];
                return true;
            }
        }
        return false;*/
        for(int i=0; i<length; i++) {    // 目的就是将数组变成,该值是什么,就存储在下标为几的地方;如果有重复的数字,那么该数字所在的下标就已经是该数字了
            if(numbers[i] == i) {}
            else if(numbers[numbers[i]] == numbers[i]) {
                duplication[0] = numbers[i];
                return true;
            } else {
                swap(numbers[i], numbers[numbers[i]]);
            }
        }
        return false;
    }
};

构建乘积数组

给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。

将B这个数组分为两部分,i-1为一部分,i+1为一部分,然后分别循环遍历

class Solution {
public:
    vector<int> multiply(const vector<int>& A) {
        vector<int> B(A.size(), 1);
        for(int i=1; i<A.size(); i++) {
            B[i] = B[i-1] * A[i-1];
        }
        int temp = 1;
        for(int i=A.size()-2; i>=0; i--) {
            temp *= A[i+1];
            B[i] *= temp;
        }
        return B;
    }
};

🌸正则表达式匹配

请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配

要注意碰到’*'的情况

class Solution {
public:
    bool match(char* str, char* pattern)
    {
        if(str == nullptr || pattern == nullptr) return false;
        if(*str == '\0' && *pattern == '\0') return true;
        if(*str != '\0' && *pattern == '\0') return false;
        if(*(pattern+1) == '*') {
            if(*str == *pattern || (*pattern == '.' && *str != '\0')) {
                return match(str, pattern + 2) || match(str + 1, pattern + 2) || match(str + 1, pattern);
            } else {
                return match(str, pattern + 2);
            }
        }
        if(*str == *pattern || (*pattern == '.' && *str != '\0')) {
            return match(str+1, pattern + 1);
        }
        return false;
    }
};

表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

注意考虑一系列的非法情况

class Solution {
public:
    bool isNumeric(char* string)
    {
        if(string == nullptr) return false;
        if(*string == '\0') return false;
        int i = 0, e_flag = 0, dot_flag = 0;
        while(*string != '\0') {
            if(!(isDigit(*string) || isE(*string) || isSig(*string) || *string == '.')) return false;    // 考虑非法字符
            if(i != 0 && isSig(*string) && !isE(*(string-1))) return false;    // 考虑正负号既没有在第一个,也没有在e后面
            if((isSig(*string) || isE(*string)) && *(string+1) == '\0') return false;    // 考虑e或者正负号后面没有数字
            if(e_flag == 0 && isE(*string)) e_flag = 1;    
            else if(e_flag != 0 && isE(*string)) return false;    // 考虑有多个e
            else if(e_flag != 0 && *string == '.') return false;    // 考虑e后面还有.
            if(dot_flag == 0 && *string == '.') dot_flag = 1;
            else if(dot_flag != 0 && *string == '.') return false;    // 考虑有多个.
            string++;
            i++;
        }
        return true;
    }
    bool isDigit(char c) {
        return c >= '0' && c <='9';
    }
    bool isE(char c) {
        return (c == 'e' || c == 'E');
    }
    bool isSig(char c) {
        return (c == '+' || c == '-');
    }
};

字符流中第一个不重复的字符

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

用map实现

class Solution
{
public:
  //Insert one char from stringstream
    void Insert(char ch)
    {
        str += ch;
        m[ch]++;
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
        for(int i=0; i<str.length(); i++) {
            if(m[str[i]] == 1) return str[i];
        }
        return '#';
    }
private:
    string str = "";
    map<char, int> m;
};

链表中环的入口节点

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

设置快慢指针,快指针每次都走两步,慢指针每次都走一步,然后记录两者相遇的点。之后快指针从该点出发继续走,慢指针从链表头开始走,两者相遇的位置就是环的入口。具体推理公式见博客

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead == nullptr) return nullptr;
        ListNode *p = pHead, *r = pHead;
        while(p->next!=nullptr && r!=nullptr) {
            p = p->next->next;
            r = r->next;
            if(p == r) break;
        }
        if(p->next == nullptr) return nullptr;
        r = pHead;
        while(p != r) {
            r = r->next;
            p = p->next;
        }
        return p;
    }
};

删除链表中重复的结点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        if(pHead == nullptr) return nullptr;
        ListNode *newHead = new ListNode(0);
        newHead->next = pHead;
        ListNode *p = pHead, *r = newHead;
        while(p) {
            if(p->next && p->val == p->next->val) {
                while(p->next && p->val == p->next->val) {
                    p = p->next;
                }
                r->next = p->next;
                p = p->next;
            }
            else {
                r = p;
                p = p->next;
            }
        }
        return newHead->next;
    }
};

二叉树的下一个结点

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

如果有右孩子,那就是右孩子的最深左结点,如果没有右孩子,那么就判断是不是父节点的左孩子,如果是左孩子,那么就是父节点,如果不是左孩子,就一直向上找父节点。

class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if(pNode->right) {
            pNode = pNode->right;
            while(pNode->left) {
                pNode = pNode->left;
            }
            return pNode;
        } 
        while(pNode->next != nullptr) {
            TreeLinkNode *parent = pNode->next;
            if(pNode == parent->left) return parent;
            pNode = pNode->next;
        }
        return nullptr;
    }
};

对称的二叉树

请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        if(pRoot == nullptr) return true;
        return isSame(pRoot->left, pRoot->right);
    }
    bool isSame(TreeNode* node1, TreeNode* node2) {
        if(node1 == nullptr && node2 == nullptr) return true;
        if(node1 == nullptr || node2 == nullptr) return false;
        if(node1->val == node2->val) return isSame(node1->left, node2->right) && isSame(node1->right, node2->left);
        return false;
    }
};

按之字形顺序打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

就是二叉树的层次遍历,但是要注意的是要记录每一层的层数,所以先记录栈中的元素个数,也就是这一层的节点数,再进行入栈

class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        if(pRoot == nullptr) return {};
        queue<TreeNode*> que;
        TreeNode *p = pRoot;
        vector<vector<int> > res;
        que.push(pRoot);
        int flag = 0;
        while(!que.empty()) {
            int size = que.size();
            vector<int> temp;
            while(size--) {
                p = que.front();
                temp.push_back(p->val);
                que.pop();
                if(p->left) que.push(p->left);
                if(p->right) que.push(p->right);
            }
            if(flag == 0) {
                res.push_back(temp);
                flag = 1;
            } else {
                reverse(temp.begin(), temp.end());
                res.push_back(temp);
                flag = 0;
            }
        }
        return res;
    }
};

把二叉树打印成多行

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

和上一道题一样

class Solution {
public:
        vector<vector<int> > Print(TreeNode* pRoot) {
            if(pRoot == nullptr) return {};
            vector<vector<int> > res;
            TreeNode *p = pRoot;
            queue<TreeNode*> que;
            que.push(p);
            while(!que.empty()) {
                int size = que.size();
                vector<int> temp;
                while(size--) {
                    p = que.front();
                    que.pop();
                    temp.push_back(p->val);
                    if(p->left) que.push(p->left);
                    if(p->right) que.push(p->right);
                }
                res.push_back(temp);
            }
            return res;
        }
};

二叉搜索树的第k个结点

给定一棵二叉搜索树,请找出其中的第k小的结点。

中序遍历,然后找第k个元素

class Solution {
public:
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(pRoot == nullptr) return nullptr;
        vector<TreeNode*> temp;
        find(temp, pRoot);
        if(temp.size() < k || k < 1) return nullptr;
        return temp[k-1];
    }
    void find(vector<TreeNode*>& temp, TreeNode *Root) {
        if(Root == nullptr) return;
        find(temp, Root->left);
        temp.push_back(Root);
        find(temp, Root->right);
    }
};

数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

动态中位数,并且是排好序的中位数,用大小两个堆实现。每个堆里都有数据流的一半数字,并且假设中位数位于大顶堆,那么数据流中的数字如果小于大顶堆的顶端,就加入到大顶堆,还有要随时比较大小顶堆的大小,大顶堆至多只能比小顶堆大1。

class Solution {
public:
    void Insert(int num)
    {
        if(max_heap.size() == 0) max_heap.push(num);
        else {
            if(num < max_heap.top()) {
                max_heap.push(num);
            } else {
                min_heap.push(num);
            }
        }
        if((max_heap.size() - min_heap.size()) == 2) {
            min_heap.push(max_heap.top());
            max_heap.pop();
        }
        if((min_heap.size() - 1) == max_heap.size()) {
            max_heap.push(min_heap.top());
            min_heap.pop();
        }
    }

    double GetMedian()
    { 
        double res;
        int size = max_heap.size() + min_heap.size();
        if(size % 2 == 0) {
            res = (max_heap.top() + min_heap.top()) / 2.0;
        } else {
            res = max_heap.top() + 0.0;
        }
        return res;
    }
private:
    priority_queue<int, vector<int>, greater<int> > min_heap;
    priority_queue<int, vector<int>, less<int> > max_heap;

};

滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

用双端队列deque来做,队列中保存的元素是可能成为最大值的元素的下标,并且队首存储的是最大值。遍历数组中的每个元素,如果一个元素大于队列中的某一元素,就把该元素从队列中删除,如果没有的话,就直接加入到队列中。同时要判断队列中的队首元素是不是超出了窗口大小,如果超出就删除。

class Solution {
public:
    vector<int> maxInWindows(const vector<int>& num, unsigned int size)
    {
        if(num.size() == 0) return {};
        if(size == 0) return {};
        deque<int> dque;
        vector<int> res;
        for(int i=0; i<num.size(); i++) {
            while(dque.size() && num[i] > num[dque.back()]) dque.pop_back();
            dque.push_back(i);
            if(dque.size() && (i-dque.front()+1) > size) dque.pop_front();
            if(i + 1 >= size) res.push_back(num[dque.front()]);
        }
        return res;
    }
};

矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。

class Solution {
public:
    int dis[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    vector<vector<int> > visited;
    bool dfs(char* matrix, int x, int y, int rows, int cols, char* str, int pos) {
        if(matrix[x*cols+y] != str[pos]) return false;
        if(pos == (int)strlen(str) - 1) return true;
        visited[x][y] = 1;
        for(int i=0; i<4; i++) {
            int next_x = x + dis[i][0];
            int next_y = y + dis[i][1];
            if(next_x >= 0 && next_x < rows && next_y >= 0 && next_y < cols && visited[next_x][next_y] == 0) {
                if(dfs(matrix, next_x, next_y, rows, cols, str, pos+1)) return true;
            }
            
        }
        visited[x][y] = 0;
        return false;
    }
    
    bool hasPath(char* matrix, int rows, int cols, char* str)
    {
        visited = vector<vector<int> >(rows,vector<int>(cols,0));
        for(int i=0; i<rows; i++) {
            for(int j=0; j<cols; j++) {
                if(matrix[i*cols+j] == str[0]) {
                    if(dfs(matrix, i, j, rows, cols, str, 0)) return true;	// 因为起始点不是(0,0),所以要先寻找起始点
                }
            }
        }
        return false;
    }
};

机器人的运动范围

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

DFS,和上一道题一样,但是要注意的是,这道题的DFS不用退回来,上一道题如果没有走通的话要退回来

class Solution {
public:
    int dis[4][2] = {{0,1}, {0,-1}, {1,0}, {-1,0}};
    vector<vector<int> > visited;
    int movingCount(int threshold, int rows, int cols)
    {
        visited = vector<vector<int> >(rows, vector<int>(cols, 0));
        int cnt = 0;
        dfs(threshold, 0, 0, rows, cols, cnt);
        return cnt;
    }
    void dfs(int threshold, int x, int y, int rows, int cols, int& cnt) {
        int sum = 0, temp_x = x, temp_y = y;
        while(temp_x) {
            sum += (temp_x%10);
            temp_x /= 10;
        }
        while(temp_y) {
            sum += (temp_y%10);
            temp_y /= 10;
        }
        if(sum > threshold) return;
        visited[x][y] = 1;
        cnt++;
        for(int i=0; i<4; i++) {
            int next_x = x + dis[i][0];
            int next_y = y + dis[i][1];
            if(next_x >=0 && next_x < rows && next_y >=0 && next_y < cols && visited[next_x][next_y] == 0) {
                dfs(threshold, next_x, next_y, rows, cols, cnt);
            }
        }
    }
};

剪绳子

给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

用动态规划,f(n) = max(f(i)*f(n-i))

class Solution {
public:
    int cutRope(int number) {
        if(number < 2) return 0;
        if(number == 2) return 1;
        if(number == 3) return 2;
        vector<int> dp(number+1, 0);
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 3;
        int max = 0;
        for(int i=4; i<=number; i++) {
            max = 0;
            for(int j=1; j<=i/2; j++) {
                int temp = dp[j] * dp[i - j];
                if(temp > max) max = temp;
            }
            dp[i] = max;
        }
        return dp[number];
    }
};

序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

class Solution {
public:
    void qxbl(TreeNode *root, vector<int>& num) {
        if(root == nullptr) {
            num.push_back(0x7fffffff);
            return;
        }
        num.push_back(root->val);
        qxbl(root->left, num);
        qxbl(root->right, num);
    }
    char* Serialize(TreeNode *root) {    
        vector<int> num;
        qxbl(root, num);
        int* str = new int[num.size()];
        for(int i=0; i<num.size(); i++) {
            str[i] = num[i];
        }
        return (char*)str;
    }
    int *s;
    TreeNode* qxcg() {
        if(*s == 0x7fffffff) {
            s++;
            return nullptr;
        }
        TreeNode *root = new TreeNode(*s);
        s++;
        root->left = qxcg();
        root->right = qxcg();
        return root;
    }
    TreeNode* Deserialize(char *str) {
        s = (int*)str;
        TreeNode *root = qxcg();
        return root;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值