剑指offer刷题

题目1 孩子们的游戏(圆圈中最后剩下的数)[题目链接](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)

思路1 :模拟,很容易想到用循环链表模拟,选择何种形式来实现循环链表的功能呢?最开始想的是用数组来模拟循环链表,用一个数组来记录数字是否出局,若出局则跳过这个数字。 看到一个比较巧妙的思路,可以用一个队列来模拟循环链表,利用队列先进先出的特点,前面报数非为m的数依次加入队列,相当于一个滑动窗口,当报数为m-1时,直接出栈。
代码1 : 数组模拟(代码略复杂,下次回来改)

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if( n<=0 || m<=0)
            return -1 ;
        if (n == 1)
            return 0;
        int exit = 0 ;
        int Count = 0, index=0;
        bool isExit[n] ;
        for(int i=0; i

代码2(队列模拟)

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if(n <=0 || m <=0)
            return -1 ;
        if(n == 1)
            return 0 ;
        queue  tmp ;
        for(int i=0; i 1)
        {
            for(int i=0; i

思路2 :找规律(具体数学中学过,待补)

题目2 扑克牌顺子[题目](https://www.nowcoder.com/practice/762836f4d43d43ca9deb273b3de8e1f4?tpId=13&tqId=11198&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
题意:判断5张牌,是否组成顺子(大小王用0,表示,A表示0,J表示11)。
思路1:判断是否为顺子:(1) 长度为5 (2)除0外没有重复数字(3)max -min <5.推广到一般情况顺子长度为k, max -min

思路2:(1)除0外没有重复数字 (2)排序后,用num[i]-num[i-1]-1记录缺几张牌,计算0的个数(可变牌数),若可变牌>=缺的牌,则可组成顺子。

代码1

class Solution {
public:
    bool IsContinuous( vector numbers ) {
        int size = numbers.size() ;
        if(size != 5)
            return false ;
        int Count[14] ={0} ;
        int Max = -1, Min=14 ;
        for(int i=0; i 1)
                return false ;
            if(numbers[i] > 13 || numbers[i] < 0)
                return false ;
            if(numbers[i] > Max)
                Max = numbers[i] ;
            if(numbers[i] < Min)
                Min = numbers[i] ;
        }
        if(Max - Min < 5)
            return true ;
        return false ;
    }
};

代码2

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

题目3 翻转单词顺序列 [题目](https://www.nowcoder.com/practice/3194a4f4cf814f63919d0790578d51f3?tpId=13&tqId=11197&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
题意:将单词序列翻转,如:“I am a student.”翻转后为“student. a am I”。
思路1 :(1)用指针p1扫描原字符串,并记录每个单词长度len,遇到空格时停止;(2)p2指向新字符串尾部;(3)p1复制给p2,p1向后走len步,p2向后走len步(4) p1+len回到空格处,将空格复制到新字符串中 。repeat上述过程,直到字符串结束。

思路2: 单指针从前往后扫描,巧妙利用string的连接操作。

代码1 :

class Solution {
public:
    string ReverseSentence(string str) {
        int length = str.length() ;
        if(length <=1)
            return str ;
        string res=str ;
        int pstr=0, pres=length-1,pstart=0;
        int len = 0 ;
        while(str[pstr] != ' ' && pstr < length)
        {
            pstr ++ ;
            len ++ ;
            if(str[pstr] ==' ')
            {
                pstart = pstr - len ;
                pstr -=1 ;
                while(pstr>=pstart)
                {
                    res[pres--]=str[pstr--];
                }
                pstr += len+1 ;
                res[pres--] = str[pstr++] ;
                len = 0 ;
            }
            if(pstr == length-1)
            {
                pstart = pstr - len ;
                while(pstr>=pstart && pres >=0)
                {
                    res[pres--]=str[pstr--];
                }
                break ;
            }
        }
        return res ;
    }
};

代码2:

class Solution {
public:
    string ReverseSentence(string str) {
        int length = str.length() ;
        if(length <= 1)
            return str ;
        string res="", tmp="" ;
        for(int i=0; i

题目4 左旋转字符串 题目链接
题意:str = XY 求输出YX
思路1 :直接利用字符串特性,取出X,Y,组合成YX
思路2:假设字符串abcdef,n=3,设X=abc,Y=def,所以字符串可以表示成XY,如题干,问如何求得YX。假设X的翻转为X,XT=cba,同理YT=fed,那么YX = (XTYT)T,三次翻转后可得结果。
代码1:

class Solution {
public:
    string LeftRotateString(string str, int n) {
        int length = str.length() ;
        if(length <= 1)
            return str ;
        n = n%length ;
        string tmp = str.substr(0,n);
        string tmp2 =str.substr(n, length-n);
        return tmp2+tmp;
         
    }
};

代码2:

class Solution {
public:
    void fun(string &s,int start,int end)
    {
        char temp;
        while(start

题目5 和为S的两个数字题目
题意:递增数列,求两个数字和为S,若有多组解,求使得乘积最小的那组。
思路:左右指针法,用两个指针从两头向中间逼近,a+b=s,a,b相差越大,a*b越小。
代码:

class Solution {
public:
    vector FindNumbersWithSum(vector array,int sum) {
        vector res ;
        int size = array.size() ;
        int plow = 0, phigh = size -1 ;
        while(plow <= phigh)
        {
            int s= array[plow] + array[phigh] ;
            if(s < sum)
            {
                plow ++ ;
            }
            if(s > sum)
            {
                phigh -- ;
            }
            if(s == sum)
            {
                res.push_back(array[plow]) ;
                res.push_back(array[phigh]) ;
                return res ;
            }
        }
        return res ;
    }
};

题目6 和为S的连续正数序列题目
题意:输出求和为S的连续正数序列,序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
思路 :双指针法,两个指针分别放在数组的第一个数字和第二个数字处,比较数字的和来移动指针,cur = (plow + phigh)*(phigh-plow+1)/2 ,cur>s, high 向左移,cur

推广到一般情况: 正数数组,求正数子序列使得 和为s。可以用前缀和数组来计算,plow 到phigh之间的数字和可以表示为 sum[phigh]-sum[plow-1]。同样地用双指针,只要前缀和数组满足递增数列就可以cur>s, high 向左移,cur

代码

class Solution {
public:
    vector > FindContinuousSequence(int sum) {
        vector > res ;
        int plow =1, phigh = 2 ;
        while(plow < phigh)
        {
            int cur = (plow + phigh)*(phigh-plow+1)/2 ;
            if(cur == sum)
            {
                vector array ;
                for(int i=plow; i<=phigh; i++)
                {
                    array.push_back(i) ;
                }
                res.push_back(array) ;
                array.clear() ;
                plow ++ ;
            }
            if(cur < sum)
                phigh ++ ;
            if(cur > sum)
                plow ++ ;
        }
        return res ;
    }
};

题目7 数组中只出现一次的两个数字题目
题意: 数组中只有两个数字只出现一次,剩下的都出现两次,找到那两个数字。

思路: 初看这题的时候,想到了异或操作, 任何数和0异或结果都是自身,相同的数异或为0。利用这个特性,如果数组中只有一个数字,那么可以找到那个数字。现在有两个这样的数字该怎么办呢?分组(分两组分别找),设只出现一次的数字是a和b,先把所有数字异或一遍,得到结果a xor b。a xor b二进制为1的位表明 a,b 在该位一个为0,一个为1. 利用这个性质把数字分为两组(相同的数字肯定在一组),a,b肯定被分在不同组,然后组内异或就可以得到结果了。
代码

class Solution {
public:
    int findFirst1(int num)
    {
        int bit = 1;
        while((num & 1) == 0  && bit < 32) //记得加括号,运算符优先级 & 比==优先级低
        {
            bit ++ ;
            num = num >> 1 ;
        }
        return bit ;
    }
    
    bool isBit1(int num, int bit)
    {
        for(int i=1; i> 1 ;
        return (num & 1) == 1 ;
    }
    
    void FindNumsAppearOnce(vector data,int* num1,int *num2) {
        int size = data.size() ;
        if(size == 2)
        {
            num1 = &data[0] ;
            num2 = &data[1] ;
        }
        int res = 0 ;
        for (int i=0; i

题目 8 平衡二叉树
题意:判断一棵树是不是平衡二叉树
思路: 递归判断树的深度差是否为1, skills 深度大于0表示正常的深度, <0表示不平衡。
代码:

class Solution {
public:
    int getDepth(TreeNode *root, int depth)
    {
        if (depth < 0 ) 
            return -1 ;
        if (root == nullptr)
            return depth ;
        int leftDepth = getDepth(root->left, depth+1);
        int rightDepth = getDepth(root->right,depth+1 ) ;
        int diff = leftDepth - rightDepth ;
        if (diff >1 || diff <-1)
            return -1 ;
        else return max(leftDepth, rightDepth) ;
    }

    bool IsBalanced_Solution(TreeNode* pRoot) {
        int depth = getDepth(pRoot, 0) ;
        if (depth < 0)
            return false ;
        else 
            return true ;
    }
};

题目 9 二叉树的深度
代码:

class Solution {
public:
    int getDepth(TreeNode *root, int depth)
    {
        if(root == nullptr)
            return depth;
        int leftDepth = getDepth(root->left, depth+1) ;
        int rightDepth = getDepth(root->right, depth+1) ;
        return max(leftDepth, rightDepth);
    }
    
    int TreeDepth(TreeNode* pRoot)
    {
        return getDepth(pRoot, 0) ;
        
    }
};

题目10 数字在排序数组中出现的次数
思路:看见数组有序就想到 二分查找, 比较巧妙的思路是 k是整数,搜索k+0.5和k-0.5应该插入的位置,两者相减就得到了。
代码:

class Solution {
public:
    int GetNumberOfK(vector data ,int k) {
        return biSearch(data, k+0.5) - biSearch(data, k-0.5) ;
    }
private:
    int biSearch(const vector & data, double num){
        int s = 0, e = data.size()-1;     
        while(s <= e){
            int mid = (e - s)/2 + s;
            if(data[mid] < num)
                s = mid + 1;
            else if(data[mid] > num)
                e = mid - 1;
        }
        return s;
    }
};

题目11 两个链表的第一个公共节点

思路:最开始的时候连题意都没有弄明白。先弄懂题意,两个链表若有公共节点的话,会表现像一个Y字,问题是两个链表长度不一样,若链表长度一样直接一一比较直到两个指针相同。skills :让长的链表先走 k步(k为两链表长度之差),然后两个链表一起走。快慢指针在链表操作中很常见,求链表的倒数第k个节点,也是先让一个指针走k步,然后另一个指针从头和当前指针一起走,当前指针走到尾节点的时候,头指针正好指向倒数第k个节点。

代码

class Solution {
public:
    int GetListLength(ListNode * head)
    {
        int length = 0 ;
        while(head)
        {
            length ++ ;
            head = head->next ;
        }
        return length ;
    }
    
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        int len1 = GetListLength(pHead1) ;
        int len2 = GetListLength(pHead2) ;
        int diff , flag;
        if (len1 > len2) 
        {
            diff = len1 - len2 ;
            flag =1 ;
        }
        else {
            diff = len2 - len1 ;
            flag = 2 ;
        }
        if (flag == 1)
        {
            for (int i=1; i<= diff; i++)
                pHead1 = pHead1->next ;
        }
            
        if (flag == 2)
        {
            for (int i=1; i <=diff; i++)
                pHead2 = pHead2->next ;
        }
        while (pHead1 != nullptr && pHead2 != nullptr && pHead1 != pHead2)
        {
            pHead1 = pHead1->next ;
            pHead2 = pHead2->next ;
        }
        return pHead1 ;
    }
};

题目 12 数组中的逆序对题目
思路:能想到归并排序这题就出来啦,记得归并的时候从后到前,从前到后会有问题。(如:1 2 7 8、4 5 9 10归并的时候从右到左,8 可以轻易判断4,5都比8小,但是从 左到右的时候, 4,5已经归并进去了,9,10大于8,这样子计数是有问题的)。主代码中不要写太多递归,容易栈溢出
代码

class Solution {
public:
    int InversePairs(vector data) {
        if(data.size()<2) return 0;
        vector copy;
        for(int i=0;i &data,vector ©,int begin,int end){
        if(begin==end){
            copy[begin]=data[begin];
            return 0;
        }
         
        int L=(end-begin)/2;
        long long left=InversePairsCore(copy,data,begin,begin+L);
        long long right=InversePairsCore(copy,data,begin+L+1,end);
        int i=begin+L;
        int j=end,inxcopy=end;
        long long count=0;
        while(i>=begin && j>=begin+L+1){
            if(data[i]>data[j]){
                copy[inxcopy--]=data[i--];
                count+=j-begin-L;
            }else copy[inxcopy--]=data[j--];
        }
        //for(i=begin;i<=end;i++) copy[i]=data[i];
        for(;i>=begin;i--) copy[inxcopy--]=data[i];
        for(;j>=begin+L+1;j--) copy[inxcopy--]=data[j];
        return left+right+count;    
    }
};

题目13 第一个只出现一次的字母
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
思路: hash 表,桶排序。
代码:

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        int len = str.length() ;
        if(len <= 0)
            return -1 ;
        int res[100] ;
        for(int i=0; i<100; i++)
            res[i] = 0; 
        for(int i=0; i< len; i++)
        {
            res[str[i] -'A'] ++ ;
        }
        for(int i=0; i< len; i++)
        {
            if(res[str[i] -'A'] == 1)
                return i ;
        }
        return -1;
    }
};

题目14 丑数
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
思路:最开始拿到这个题目的时候,第一反应是找规律(希望能发现数字间的规律)结果发现没有规律,不要直接陷入找规律中有些规律可能不是以数字的形式呈现,可以多考虑堆栈队列等数据结构。
丑数是质因子只有2、3、5的数,也就是说每个丑数是由原来的丑数乘以2、3、5得到的,1乘以2、3、5得到2,3,5 依次乘以2,3,5得到4,6,10 ,6 ,9,15,10,15,25。这样得到的序列不是有序的且有重复。可以用3个队列来维护*2,*3,*5的结果。
1)丑数数组: 1
乘以2的队列:2
乘以3的队列:3
乘以5的队列:5
选择三个队列头最小的数2加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(2)丑数数组:1,2
乘以2的队列:4
乘以3的队列:3,6
乘以5的队列:5,10
选择三个队列头最小的数3加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(3)丑数数组:1,2,3
乘以2的队列:4,6
乘以3的队列:6,9
乘以5的队列:5,10,15
选择三个队列头里最小的数4加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(4)丑数数组:1,2,3,4
乘以2的队列:6,8
乘以3的队列:6,9,12
乘以5的队列:5,10,15,20
选择三个队列头里最小的数5加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
1.为什么分三个队列?
丑数数组里的数一定是有序的,因为我们是从丑数数组里的数乘以2,3,5选出的最小数,一定比以前未乘以2,3,5大,同时对于三个队列内部,按先后顺序乘以2,3,5分别放入,所以同一个队列内部也是有序的
2.为什么比较三个队列头部最小的数放入丑数数组?
因为三个队列是有序的,所以取出三个头中最小的,等同于找到了三个队列所有数中最小的。
实现思路:
我们没有必要维护三个队列,只需要记录三个指针显示到达哪一步;

代码:

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if (index < 7)
            return index ;
        vector res(index) ;
        res[0] = 1 ;
        int p2=0, p3=0, p5=0 ;
        for(int i=1; i< index; i++)
        {
            res[i] = min(res[p2]*2, min(res[p3]*3, res[p5]*5)) ;
            if(res[p2]*2 == res[i]) p2++ ;
            if(res[p3]*3 == res[i]) p3++ ;
            if(res[p5]*5 == res[i]) p5++ ;
        }
        return res[index -1] ;
    }
};

你可能感兴趣的:(leet,code,leetcode)