剑指offer(二)

11. 二进制中1的个数

二进制中1的个数_牛客题霸_牛客网 (nowcoder.com)

法一:

使用库函数bitset

class Solution {
public:
    int NumberOf1(int n) {
        return bitset<32>(n).count();
    }
};

法二:

手动位运算。对于n=1100,n-1=1011。n &= n-1后,n=1000。这个过程相当于从二进制数中消去了一个1。

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

12. 数值的整数次方

数值的整数次方_牛客题霸_牛客网 (nowcoder.com)

LCR 134. Pow(x, n) - 力扣(LeetCode)

法一:

常规解法,区分正负即可。

class Solution {
public:
    double Power(double base, int exponent) {
        if(exponent == 0) return 1.0;
        if(base == 0) return 0.0;
        bool flag = false;
        if(exponent < 0){
            flag = true;
            exponent *= -1;
        }
        double ans = 1.0;
        for(int i = 0;i<exponent;i++){
            ans*= base;
        }
        if(flag) ans = 1/ans;
        return ans;
    }
};

法二:

快速幂:把幂次按照二进制拆开,分别计算。例如,计算一个数的10次方相当于计算一个数的1010(二进制)次方,可以看作按照x的2次方一次递增。

class Solution {
public:
    double Power(double base, int exponent) {
        if(exponent == 0) return 1;
        if(base == 0) return 0;
				// exp一定要用long。假设exponent=INT_MIN,其对应的正数大于INT_MAX,故不能用int
        long exp = exponent;
        if(exponent < 0){
            exp = exponent *(-1.0);
        }
        double ans = 1.0;
        while(exp != 0){
            if((exp & 1) == 1){
                ans *= base;
            }
            base *= base;
            exp >>= 1;
        }
        return exponent < 0 ? 1 /ans : ans;

    }
};

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

调整数组顺序使奇数位于偶数前面_牛客题霸_牛客网 (nowcoder.com)

法一:

使用辅助数组

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        vector<int> vec;
        for(auto& i : array)
            if(i & 1) array.push_back(i);
        for(auto& i : array)
            if(!(i & 1)) array.push_back(i);
        
        copy(vec.begin(),vec.end(),array.begin());
    }
};

法二:

in-place算法。

i记录当前需要插入的奇数的位置,用j遍历数组。如果遇到奇数,就将其插入到i所在的位置(ij-1之间所有的数据往后挪一位)。

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        int i = 0;
        for(int j = 0;j<array.size();j++){
            if(array[j] & 1){
                int temp = array[j];
                for(int k = j-1;k>=i;--k){
                    array[k+1] = array[k];
                }
                array[i++] = temp;
            }
        }
    }
};

法三:

使用STL函数****stable_partition()****对指定区域内的数据进行分组,重新排列指定区域内存储的数据,使其分为两组,第一组位符合筛选条件的数据,另一组为不符合筛选条件的数据。

函数原型:

template< class BidirIt, class UnaryPredicate >

BidirIt stable_partition( BidirIt first, BidirIt last, UnaryPredicate p );

第三个参数可以传入一个仿函数,函数指针,lambda表达式。

#include <algorithm>
class Solution {
public:
    void reOrderArray(vector<int> &array) {
        stable_partition(array.begin(),array.end(),[](int x){return x & 1;});
    }
};

14. 链表中倒数第k个结点

链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)

双指针

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
		if(!pListHead || k <= 0) return nullptr;
		auto slow = pListHead,fast = pListHead;
		int n = 0;
		while(k--){
			if(fast)
				fast = fast->next;
			else
			 	return nullptr;
		}
		while(fast){
			fast = fast->next;
			slow = slow->next;
		}
		return slow;
    }
};

15. 反转链表

反转链表_牛客题霸_牛客网 (nowcoder.com)

法一:

双指针。以1,2,3链表为例,用三个指针,

  • cur指针用来遍历,指向head
  • pre指向cur的前一个节点,故一开始其必须初始化为nullptr
  • 由于在遍历过程中需要将cur->next指向pre。故还需要一个temp指针用来保存cur->next实现遍历。
class Solution {
public:
    ListNode* ReverseList(ListNode* head) {
        if(head == nullptr) return nullptr;
        ListNode* dummyNode = new ListNode(0);
        ListNode* cur = head;
        ListNode* pre = nullptr;
        ListNode* temp = nullptr;
        while(cur){
            temp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

法二:

递归。一次反转整个链表很困难,但反转第一个元素和第二个元素比较容易。

  • 故将原问题可以减小为反转前两个元素。依次往后递归。
  • 在思考时,假定ReverseList(head->next)代表已经反转好的后续元素。只需要改变链表前两个元素的指针指向并考虑边界条件即可。
  • 边界条件:head == nullptr代表链表本身为空,head->next == nullptr代表递归出口
class Solution {
public:
    ListNode* ReverseList(ListNode* head) {
        if(head == nullptr) return nullptr;
        if(head->next == nullptr) return head;
        ListNode* ans = ReverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return ans;
    }
};

16. 合并两个有序链表

合并两个排序的链表_牛客题霸_牛客网 (nowcoder.com)

经典题目。起一个虚拟头节点,然后一边遍历,一边将小的插在都节点后面,直到两个链表有一个为空。最后将不为空的链表插入到结果的后面即可。

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        if(pHead1 == nullptr) return pHead2;
        if(pHead2 == nullptr) return pHead1;
        ListNode* dummyNode = new ListNode(0);
        ListNode* cur = dummyNode;
        while(pHead1  && pHead2){
            if(pHead1->val < pHead2->val){
                cur->next = pHead1;
                pHead1 = pHead1->next;
            }else{
                cur->next = pHead2;
                pHead2 = pHead2->next;
            }
            cur = cur->next;
        }
        if(pHead1) cur->next = pHead1;
        if(pHead2) cur->next = pHead2;
        return dummyNode->next;
    }
};

17. 树的子结构

树的子结构_牛客题霸_牛客网 (nowcoder.com)

树的问题归根结底的递归问题。

根据题意,判断B是不是A的子结构。由于题意表明空树不是任意一个树的子结构,故一开始就将空树进行处理(直接return false)。

之后判断B是否是A的子结构。若B是,则A的任意一个节点都有可能是B的根节点。故需要先序遍历A的每个节点判断以A中的节点node为根节点的子树是否包含树B。(isSameTree)在该函数中,

  • 终止条件:
    • B为空,表示,B已匹配完成,返回true
    • A为空,表示已经越过A的叶节点,即匹配失败,返回false
    • A和B的值不同:表明匹配失败,返回false
  • 返回值:到了最后返回的时候表明A和B的根节点相同,故需要判断其左右子树是否相等。故返回值为isSameTree(pRoot1->left,pRoot2->left) && isSameTree(pRoot1->right,pRoot2->right);
class Solution {
public:
	bool isSameTree(TreeNode* pRoot1,TreeNode* pRoot2){
		if(pRoot2 == nullptr) return true;
		if(pRoot1 == nullptr || pRoot1->val != pRoot2->val) return false;
		return isSameTree(pRoot1->left,pRoot2->left) && isSameTree(pRoot1->right,pRoot2->right);
	}
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
		if(pRoot1 == nullptr || pRoot2 == nullptr) return false;
		return isSameTree(pRoot1,pRoot2) || HasSubtree( pRoot1->left, pRoot2) || HasSubtree(pRoot1->right,pRoot2);
    }
};

18. 二叉树的镜像

二叉树的镜像_牛客题霸_牛客网 (nowcoder.com)

只要能遍历一遍所有节点,然后交换每个节点的左右孩子即可。故三种遍历及其迭代法改造以及层序遍历都可以。

法一:

理解了递归,就理解了树。这里用先序遍历,先交换节点的左右子节点,之后分别递归处理左右子树。

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

法二:

先序迭代法

class Solution {
public:
    TreeNode* Mirror(TreeNode* pRoot) {
        if(pRoot == nullptr) return nullptr;
        stack<TreeNode*> st;
        st.push(pRoot);
        while(!st.empty()){
            TreeNode* cur = st.top();
            st.pop();
            swap(cur->left,cur->right);
            if(cur->left) st.push(cur->left);
            if(cur->right) st.push(cur->right);
        }
        return pRoot;
    }
};

法三:

中序遍历。采用递归实现的中序遍历,部分节点的左右孩子会反转两次。故与传统中序写法不太一致。

class Solution {
public:
    TreeNode* Mirror(TreeNode* pRoot) {
        if(pRoot == nullptr) return nullptr;
        Mirror(pRoot->left);   // 左
        swap(pRoot->left,pRoot->right);  //中
        Mirror(pRoot->left);  //“右”,因为左右节点已经交换了,所赐此时的left为原来的right
        return pRoot;
    }
};

19. 顺时针打印矩阵

顺时针打印矩阵_牛客题霸_牛客网 (nowcoder.com)

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        vector<int> ans;
        if(matrix.empty()) return ans;
        int rl = 0, rh = matrix.size()-1;
        int cl = 0, ch = matrix[0].size()-1;
        while(1){
            for(int i = cl;i<=ch;i++) ans.push_back(matrix[rl][i]);
            if(++rl > rh) break;
            for(int i = rl;i<=rh;i++) ans.push_back(matrix[i][ch]);
            if(--ch < cl) break;
            for(int i = ch;i >= cl ;i--) ans.push_back(matrix[rh][i]);
            if(--rh < rl) break;
            for(int i = rh;i >= rl;i--) ans.push_back(matrix[i][cl]);
            if(++cl > ch) break;
        }
        return ans;
    }
};

20. 包含min函数的栈

包含min函数的栈_牛客题霸_牛客网 (nowcoder.com)

法一:

用一个栈,每次存完以后额外存一下最小值。

class Solution {
public:
    void push(int value) {
        if(st.empty()){
            st.push(value);
            st.push(value);
        }else{
            int mi = st.top();
            mi = value < mi ? value : mi;
            st.push(value);
            st.push(mi);
        }
    }
    void pop() {
        if(st.empty()) return ;
        st.pop();
        st.pop();
    }
    int top() {
        if(st.empty()) return -1;
        int temp = st.top();
        st.pop();
        int ans = st.top();
        st.push(temp);
        return ans;
    }
    int min() {
        if(st.empty()) return -1;
        return st.top();
    }
private:
    stack<int> st;
};

法二

也可以用两个栈,一个存数据,另一个存最小值。跟上面原理一样。

class Solution {
public:
    void push(int value) {
        if(st.empty()){
            st.push(value);
            minSt.push(value);
        }else{
            int mi = minSt.top();
            mi = value < mi ? value : mi;
            minSt.push(mi);
            st.push(value);
        }
    }
    void pop() {
        if(st.empty()) return ;
        st.pop();
        minSt.pop();
    }
    int top() {
        if(st.empty()) return -1;
        return st.top();
    }
    int min() {
        if(st.empty()) return -1;
        return minSt.top();
    }
private:
    stack<int> st;
    stack<int> minSt;
};

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

记与思

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值