剑指offer(第二版)C++笔记(一)


栈与队列

09. 用两个栈实现队列

题目

在这里插入图片描述

思路

  • 用两个栈a1和a2来模拟队列,a1模拟队尾的插入,a2模拟队头的删除
  • 考虑栈a2中可能还有数据,将全部数据移动到a1中,再插入
  • 考虑栈a1中可能还有数据,将全部数据移动到a2中,再删除
  • 考虑队列中没有元素,则返回-1,且用变量保存要删除的数据

代码

class CQueue {
public:
    CQueue() {
        
    }
    void appendTail(int value) {
        while(!a2.empty())
        {
            a1.push(a2.top());
            a2.pop();
        }
        a1.push(value);
    }
    
    int deleteHead() {
        while(!a1.empty())
        {
            a2.push(a1.top());
            a1.pop();
        }
        if(a2.empty())
            return -1;
        else
        {
            num = a2.top();
            a2.pop();
            return num;
        }      
    }

public:
    stack<int> a1;
    stack<int> a2;
    int num = -1;
};

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */

30.包含min函数的栈

题目

在这里插入图片描述

思路

  • 难点在于如何使min的时间复杂度为O(1)
  • 可以采用一个辅助栈,存储每个区间中的最小值

代码

class MinStack {
public:
    /** initialize your data structure here. */
    MinStack() {

    }
    
    void push(int x) {
        a.push(x);
        if(b.empty())
        {
            b.push(x);
        }
        else
        {
            value = (x<b.top())?x:b.top();
            b.push(value);
        }
    }
    
    void pop() {
        a.pop();
        b.pop();
    }   
    
    int top() {
        return a.top();
    }
    
    int min() {
        return b.top();
    }
public:
    stack<int> a,b;
    int value;
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->min();
 */

链表

06. 从尾到头打印链表

题目

在这里插入图片描述

思路

  • 将每个节点的数据压入数组,然后转置数组
  • vector数组插入:push_back();
  • 数组的转置: std::reverse(a.begin(), a.end());

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> a;
        while(head)
        {
            a.push_back(head->val);
            head = head->next;
        }
        // 数组转置
       std::reverse(a.begin(), a.end());
       return a;
    }
};

24. 反转链表

题目

在这里插入图片描述

思路

  • 创建哨兵节点,解除头结点的特殊性
  • 用头插法来反转链表
  • 需要考虑链表为空的情况

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL) 
            return NULL;
        
        ListNode* a = new ListNode(1);
        a->next = head;

        while(head->next)
        {
            ListNode* b = head->next;
            head->next = head->next->next;   // 取出b节点
            // 头插法
            b->next = a->next;
            a->next = b;
        }
        return a->next;
    }
};

35. 复杂链表的复制

题目

在这里插入图片描述

回溯+哈希

思路
  • 如果是普通链表,我们可以直接按照遍历的顺序创建链表节点。因为随机指针的存在,其指向的节点可能还没创建,因此需要转化思路
  • 用哈希表记录当前每一个节点对应新节点的情况,拷贝当前节点
  • 创建新节点后,检查【当前节点的后继节点】和【当前节点的随机指针指向的节点】的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们立刻递归地进行创建
  • 为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针返回即可
代码
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    unordered_map<Node*, Node*> cachedNode;

    Node* copyRandomList(Node* head) {
        if(head == nullptr)
            return nullptr;

        // 防止重复拷贝新节点
        //count函数:返回哈希桶中关键码为key的键值对的个数,查看是否存在
        if(!cachedNode.count(head))
        {
            Node* NewNode = new Node(head->val);
            cachedNode[head] = NewNode;
            NewNode->next = copyRandomList(head->next);
            NewNode->random = copyRandomList(head->random);
        }
        return cachedNode[head];
    }
};

迭代+拆分

思路
  • 可以将链表每一个节点拆分为两个相连的节点, 比如A ->B ->C 拆分为 A ->A’ ->B ->B’ ->C ->C‘
  • 对于任意一个原节点S,其拷贝节点S’就是其后继节点
  • 我们可以直接找到每一个拷贝节点S’的随机指针应当指向的节点
代码
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr)
            return nullptr;
        for(Node* node = head; node != nullptr; node = node->next->next)
        {
            // 创建新节点
            Node* nodeNew = new Node(node->val);
            // 接在原节点后面
            nodeNew->next = node->next;
            node->next = nodeNew;
        }
        for(Node* node = head; node != nullptr; node = node->next->next)
        {
            Node* nodeNew = node->next;
            nodeNew->random = (node->random != nullptr)? node->random->next: nullptr;
        }
        
        Node* headnew = head->next;
        for(Node* node = head; node != nullptr; node = node->next)
        {
            // 记录新节点
            Node* nodeNew = node->next;
            // 将新节点取出
            node->next = node->next->next;
            nodeNew->next = (node->next != nullptr)? node->next->next: nullptr;
        }
        return headnew;
    }
};

字符串

05. 替换空格

题目

在这里插入图片描述

代码

class Solution {
public:
    string replaceSpace(string s) {
        string array;

        for(char& ch: s)
        {
            if(ch == ' ')
                array += "%20";
            else 
                array += ch;
        }

        return array;
    }
};

58. ΙΙ. 左旋转字符串

题目

在这里插入图片描述

代码

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        string a;
        for(int i=n; i<s.size(); i++)
        {
            a += s[i];
        }
        for(int i=0; i<n; i++)
        {
            a += s[i];
        }
        return a;
    }
};

查找算法

03. 数组中重复的数字

题目

在这里插入图片描述

代码

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        
        for(auto& ch: nums)
        {
            // 之前有出现过,m[ch]为1
            if(m[ch] == 1)
                return ch;
            m[ch]++;
        }
        return -1;
    }
public:
    unordered_map<int, int> m;
};

官方题解

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_map<int, bool> array;

        for(int i=0; i<nums.size(); i++)
        {
            // 出现重复
            if(array[nums[i]])
            {
                return nums[i];
            }
            array[nums[i]] = true;
        }
        return -1;
    }
};

53. Ι. 在排序数组中查找数字

题目

在这里插入图片描述

暴力题解

思路
  • 遍历一次,找到第一个目标值的位置
  • 从这个位置往后开始计数,得到目标值出现的次数
代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        // 暴力
        int count = 0;
        for(auto& sh: nums)
        {
            if(sh == target)
                count++;
        }
        return count;
    }
};

二分查找

思路
  • 用二分查找算法得到目标值第一次出现的位置和大于目标值的位置
  • 因为是有序数组,相减得到重复的次数
代码
class Solution {
public:
    int binarySearch(vector<int>& nums, int target, bool lower)
    {
        int left = 0, right = nums.size()-1, ans = nums.size();
        while(left <= right)
        {
            // 取中
            int mid = left + (left -  right) / 2;
            // 大于目标,则在左边,移动right
            if(nums[mid] > target || (lower && nums[mid] >= target) )
            {
                // lower为true时,right在第一个target的左边。lower为false时,right是最后一个target
                right = mid-1; 
                ans = mid;
            } 
            else
            {
                left = mid+1;
            }
        }
        return ans;
    }
    int search(vector<int>& nums, int target) {
        int leftIdx = binarySearch(nums, target, true);
        int rightIdx = binarySearch(nums, target, false) - 1;
        if(leftIdx <= rightIdx && rightIdx < nums.size() && nums[leftIdx] == target && nums[rightIdx] == target)
        {
            return rightIdx - leftIdx + 1;
        }
        return 0;
    }
};

53. ΙΙ. 0~n-1中缺失的数字

题目

在这里插入图片描述

思路

  • 在有序数组中查找数字,可以用二分查找
  • 因为是从0开始连续,考虑条件为是否等于所在位置
  • 左子数组:nums【i】 == i;
  • 右子数组:nums【i】!= i;
  • 跳出时,变量left和right分别指向“右子数组的首位元素”和“左子数组的末位元素”, 因此返回left即可

代码

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int left = 0, right = nums.size()-1;
        while(left <= right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] == mid)
            {
                // 相等表示前面是没有缺失数,按序的
                // 在右半部分查找
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        // 缺失的数字是最后一次循环的mid+1,故返回left
        return left;
    }
};

04. 二维数组中的查找

题目

在这里插入图片描述

思路

  • 线性查找
  • 从二维数组的右上角开始查找
  • 如果当前元素等于目标值,则返回true。如果当前元素大于目标值,则移动左边一列。如果当前元素小于目标值,则移到下一行

代码

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        if(matrix.size() == 0 || matrix[0].size() == 0)
            return false;
        
        int rows = matrix.size(), columns = matrix[0].size();
        // 从右上角开始, 第一行最后一列
        // row表示行,column表示列
        int row = 0, column = columns-1;
        while(row <rows && column >= 0)
        {
            if(matrix[row][column] == target)
                return true;
            // 若大于目标值, 则往左移, 否则往下移
            else if(matrix[row][column] > target)
            {
                column--;
            }else{
                row++;
            }
        }
        return false;
    }
};

11. 旋转数组的最小数字

题目

在这里插入图片描述

思路

  • 旋转数组后,数组分为两部分:左排序递增数组和右排序递增数组,且左边数组的值大于等于右边数组的值
  • 我们的目标是寻找右排序数组的最小元素x,设m=( i + j ) / 2 为每次二分算法的中点
  • 当 nums[m] > nums[j] 时, m一定在左排序数组中,即旋转点x一定在【m+1,j】闭区间内,因此执行i=m+1
  • 当 nums[m] < nums[j] 时, m一定在右排序数组中,即旋转点x一定在【i, m】闭区间内, 因此执行j = m
  • 当 nums[m] = nums[j] 时, 无法判断m在哪个排序数组中,执行j = j-1 缩小判断范围
  • 返回值:当i=j时跳出二分循环,并返回旋转点的值nums[i]

代码

class Solution {
public:
    int minArray(vector<int>& numbers) {
       int left = 0, right = numbers.size()-1;
       while(left < right)
       {
           int mid = left + (right - left) /2;
           if(numbers[mid] < numbers[right])
           {
               right = mid; // 可以忽略右半部分
           }
           else if(numbers[mid] > numbers[right])
           {
               left = mid+1; // 可以忽略左半部分
           }
           else
           {
               right -= 1;
           }
       }
       return numbers[left];
    }
};

50. 第一次只出现一次的字符

题目

在这里插入图片描述

思路

  • 用散列表记录字符出现的频次

代码

class Solution {
public:
    char firstUniqChar(string s) {
        char a = ' ';
        unordered_map<char, int> map;

        if(s.size() == 0)
            return a;
        for(auto& ch: s)
        {
            map[ch]++;
        }
        for(auto& ch: s)
        {
            if(map[ch] == 1)
                return ch;
        }
        return a;
    }
};

参考文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值