剑指offer12~22题

机器人的运动范围

class Solution {
public:
    //计算单个数字的各位之和
    int get_single_sum(int x) {
        int s = 0;
        while (x) {
            s += x %10;
            x /= 10;
        }
        return s;
    }
    //计算点的各位之和
    int get_sum(pair<int, int> p) {
        return get_single_sum(p.first) + get_single_sum(p.second);
    }

    int movingCount(int threshold, int rows, int cols) {
        int res = 0;
        if (!rows || !cols) return 0;//特判
        
        vector<vector<bool>> st(rows, vector<bool>(cols));//之前没有遍历过,用bool数组判断,初始化为false
        queue<pair<int, int>> q;
        q.push({0, 0});//第一个点
        
        int dx[4] = {-1, 0, 1, 0};//常用技巧,上右下左顺时针
        int dy[4] = {0, 1, 0, -1};
        
        while (q.size()){
            auto t = q.front();//取出队列头
            q.pop();//弹出 
            //开始遍历
            if(get_sum(t) > threshold || st[t.first][t.second]) continue;//如果大于k并且之前遍历过则跳过
            res++;//否则可以走,+1
            st[t.first][t.second] = true;//把当前点标记为已遍历过
            //遍历周围的四个点
            for (int i = 0; i < 4; i++) {
                int x = t.first + dx[i], y = t.second + dy[i];
                if (x >= 0 && x < rows && y >= 0 && y < cols) {
                    q.push({x, y});
                }
            }
        }
        
        return res;
        
    }
};

剪绳子
一个经典的数学问题,可以通过数学求导证明取e的时候得到极值,但由于是整数,所以向上取整得3.这里提供另一种证明方法:
在这里插入图片描述

class Solution {
public:
    int maxProductAfterCutting(int length) {
        int n = length;
        if (n <= 3)  return 1 * (n - 1);
        
        int res = 1;
        if (n % 3 == 1) res *=4, n -=4;
        if (n % 3 == 2) res *=2, n -=2;
        while(n) {
            res *= 3;
            n -=3;
        }
        
        return res;
    }
};

二进制中1的个数

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

数值的整数次方
这题主要考点就是快速幂以及对于负数得处理。

class Solution {
public:
    double Power(double base, int exponent) {
        double res = 1;
        int k = abs(exponent);
        while(k) {
            if (k & 1)  res = res * base;
            k >>= 1;
            base = base * base;
        }
        if (exponent < 0)   return 1/res;
        return res;
    }
};

在O(1)时间删除链表结点
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    void deleteNode(ListNode* node) {
        node->val = node->next->val;
        node->next = node->next->next;
    }
};

删除链表中重复的节点

在这里插入图片描述
本题最大的考点是双指针,这个双指针的总结如下,头指针指向当前最后一个非重复元素所在的结点pi,第二个指针指向的是下一个元素的下一个结点pj,从而通过判断pj是否为pi的next的next来判断pi和pj之间是否只有一个元素,是的话,则将pi移到pi的next,否则的话将pi的next指针指向pj,此时的做法相当于直接删除了pi和pj之间的重复元素.

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplication(ListNode* head) {
        auto dummy = new ListNode(-1);//初始化一个dummy节点
        dummy->next = head;//指向头节点
        auto p = dummy;
        while(p->next) {
            auto q = p->next;//双指针,p存得是上一次保留的节点的尾节点,q则是下一段的头节点
            while(q && p->next->val == q->val)  q = q->next;//如果说p的下一个节点值与q的值的相同,q就一直往后走
            
            if (p->next->next == q) p = p->next;//判断下一段长度为1,则不删除,p往后顺移一位
            else p->next = q;//长度不为1,则删去
        }
        
        return dummy->next;
    }
};

正则表达式匹配
这一题还是很有难度的。
在这里插入图片描述

class Solution {
public:
    vector<vector<int>>f;
    int n, m;
    bool isMatch(string s, string p) {
        n = s.size();
        m = p.size();
    //为什么不是n,m 因为考虑到s,p都为空的情况
        f = vector<vector<int>>(n + 1, vector<int>(m + 1, -1));
        return dp(0, 0, s, p);
    }

    bool dp(int x, int y, string &s, string &p)
    {
    //利用到的f[x][y] 有值说明已经计算过,直接返回不需要再去递归。利用到已经计算到的值,截枝不让重复递归
        if (f[x][y] != -1) return f[x][y];
        if (y == m)
            return f[x][y] = x == n;
        bool first_match = x < n && (s[x] == p[y] || p[y] == '.');
        bool ans;
        if (y + 1 < m && p[y + 1] == '*')
        {
            ans = dp(x, y + 2, s, p) || first_match && dp(x + 1, y, s, p);
        }
        else
            ans = first_match && dp(x + 1, y + 1, s, p);
        return f[x][y] = ans;
    }
};

表示数值的字符串

在这里插入图片描述

class Solution {
public:
    bool isNumber(string s) {
        int i = 0;
        while (i < s.size() && s[i] == ' ') i ++ ;
        int j = s.size() - 1;
        while (j >= 0 && s[j] == ' ') j -- ;
        if (i > j) return false;
        s = s.substr(i, j - i + 1);

        if (s[0] == '-' || s[0] == '+') s = s.substr(1);
        if (s.empty() || s[0] == '.' && s.size() == 1) return false;

        int dot = 0, e = 0;
        for (int i = 0; i < s.size(); i ++ )
        {
            if (s[i] >= '0' && s[i] <= '9');
            else if (s[i] == '.')
            {
                dot ++ ;
                if (e || dot > 1) return false;
            }
            else if (s[i] == 'e' || s[i] == 'E')
            {
                e ++ ;
                if (i + 1 == s.size() || !i || e > 1 || i == 1 && s[0] == '.') return false;
                if (s[i + 1] == '+' || s[i + 1] == '-')
                {
                    if (i + 2 == s.size()) return false;
                    i ++ ;
                }
            }
            else return false;
        }
        return true;
    }
};

解法二:使用指针巧解表示数值的字符串
核心思想为使用一个指针从前往后逐个检查字符串中的字符是否合法,如果合法,则指针后移,否则指针停止移动,显然如果字符串是合法的,这个指针应该移动到字符串的最末尾

同时在移动指针的过程中判断从字符串开始到当前位置的字符子串是否是合法的数值,并将它存储在isNum中,显然isNum记录了指针所指位置的字符子串是否能表示为数值的信息

指针的最终位置和isNum的值决定最终的结果

class Solution {
public:
    bool isNumber(string s) {
        s += '\0';   //指针移动过程中,最后会有一位的溢出,加上一位空字符防止字符串下标越界
        bool isNum = false; //该变量表示从0开始,到i位置的字符串是否构成合法数字,初始化为false

        int i = 0;  //检测指针初始化为0

        while(s[i] == ' ') ++i; //滤除最前面的空格,指针后移

        if(s[i] == '+' || s[i] == '-') ++i; //一个‘-’或‘+’为合法输入,指针后移

        while(s[i] >= '0' && s[i] <= '9'){  //此处如果出现数字,为合法输入,指针后移,同时isNum置为true
            isNum = true;  //显然,在此处,前面的所有字符是可以构成合法数字的
            ++i;
        }

        if(s[i] == '.') ++i;    //按照前面的顺序,在此处出现小数点也是合法的,指针后移(此处能否构成合法字符取决于isNum)

        while(s[i] >= '0' && s[i] <= '9'){  //小数点后出现数字也是合法的,指针后移
            isNum = true;   //无论前面是什么,此处应当是合法数字
            ++i;
        }

        //上面的部分已经把所有只包含小数点和正负号以及数字的情况包括进去了,如果只判断不含E或e的合法数字,到此处就可以停止了

        if(isNum && (s[i] == 'e' || s[i] == 'E')){ //当前面的数字组成一个合法数字时(isNum = true),此处出现e或E也是合法的
            ++i;
            isNum = false; //但到此处,E后面还没有数字,根据isNum的定义,此处的isNum应等于false;

            if(s[i] == '-' || s[i] == '+') ++i; //E或e后面可以出现一个‘-’或‘+’,指针后移

            while(s[i] >= '0' & s[i] <= '9') {
                ++i;
                isNum = true; //E后面接上数字后也就成了合法数字
            }
        }

        //如果字符串为合法数字,指针应当移到最后,即是s[i] == '\0' 同时根据isNum判断数字是否合法
        //整个过程中只有当i位置处的输入合法时,指针才会移动
        return (s[i] == '\0' && isNum);
    }
};

调整数组顺序使奇数位于偶数前面
类似于快排中的双指针思想。
在这里插入图片描述

class Solution {
public:
    void reOrderArray(vector<int> &array) {
         int l = 0 - 1, r = array.size();
         while(l < r) {
            do l++; while(l < r && array[l] % 2 == 1);//第一个指针一直往后走,直到遇到第一个偶数为止
            do r--; while(l < r && array[r] % 2 == 0);//第二个指针一直往前走,直到遇到第一个奇数为止
             if(l < r)  swap(array[l], array[r]);//交换两个指针指向的位置上的数,再进入下一层迭代,直到两个指针相遇为止;
         }
    }
};

链表中倒数第k个节点
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* findKthToTail(ListNode* pListHead, int k) {
        int n = 0;
        for (auto p = pListHead; p; p = p->next)  n++;
        if (k > n)  return nullptr;
        auto p = pListHead;
        for (int i = 1; i < n - k + 1; i++) p = p->next;
        return p;
    }
};

链表中环的入口结点

推荐题解
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *entryNodeOfLoop(ListNode *head) {
        if (!head || !head->next)   return 0;
        auto i = head, j = head;//i是慢的,j是快的
        
        while (i && j) {
            i = i->next;
            j = j->next;
            if (j)  j = j->next;//j多走一步
            else return 0;//如果走到空节点,则说明没有环,返回null
            //两者相遇则证明有环,将慢指针退回起点,再次相遇则在入口位置
            if (i == j) {
                i = head;
                while (i != j) {
                    i = i->next;
                    j = j->next;
                }
                return i;
            }
        }
        return 0;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值