剑指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<n;i++)
            isExit[i] = false ;
        while(exit <n-1)
        {
            if(index == n)
                index = 0 ;
            //cout << "Count is " << Count << endl ;
            //cout <<"index is " << index << endl ;
            if(Count == m-1)
            {
                //cout <<"exit " << index << endl ;
                isExit[index] = true ;
                exit ++ ;
                index ++ ;
                if(index == n)
                    index = 0 ;
                while(isExit[index] == true)
                {
                    index ++ ;
                    if(index == n)
                        index = 0;
                }
                Count = 0;
            }
            else
            {
                    index ++ ;
                    if(index == n)
                        index = 0 ;
                    while(isExit[index] == true)
                    {
                        index ++ ;
                        if(index == n)
                            index = 0;
                    }
                    Count ++ ;
            }
 
        }
        for(int i=0; i<n; i++)
            if(isExit[i] == false)
                return 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 <int> tmp ;
        for(int i=0; i<n ;i++)
            tmp.push(i) ;
        while(tmp.size() > 1)
        {
            for(int i=0; i<m-1; i++)
            {
                int front = tmp.front() ;
                tmp.pop() ;
                tmp.push(front) ;
            }
            tmp.pop() ;
        }
        return tmp.front() ;
    }
};

思路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 <k。

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

代码1

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        int size = numbers.size() ;
        if(size != 5)
            return false ;
        int Count[14] ={0} ;
        int Max = -1, Min=14 ;
        for(int i=0; i<size; i++)
        {
            Count[numbers[i]]++ ;
            if(numbers[i] == 0)
                continue ;
            if(Count[numbers[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<int> 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; i<size; i++)
        {
            if(numbers[i] == 0)
            {
                Count ++ ;
            }
            if(numbers[i] >0)
            {
                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<length; i++)
        {
            if(str[i]==' ')
            {
                res =" "+tmp+res ;
                tmp="";
            }
            else tmp += str[i] ;
        }
        if(tmp.size()) res = tmp + res;
        return res ;
    }
};

题目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<end)
        {
            temp=s[start];
            s[start]=s[end];
            s[end]=temp;
            start++;
            end--;
        }
    }
    string LeftRotateString(string str, int n) {
        int len=str.length();
        if(0==len || 0==n)
            return str;
        string &temp=str;
        fun(temp,0,n-1);
        fun(temp,n,len-1);
        fun(temp,0,len-1);
        return str;
    }
};

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

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        vector<int> 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右移(递增数列的性质),cur=s, 得到一组解,plow右移(或者high 左移)继续找另一组解。 因为是等差数列,所以可以直接用公式求和。

推广到一般情况: 正数数组,求正数子序列使得 和为s。可以用前缀和数组来计算,plow 到phigh之间的数字和可以表示为 sum[phigh]-sum[plow-1]。同样地用双指针,只要前缀和数组满足递增数列就可以cur>s, high 向左移,cur <s plow右移(递增数列的性质),cur=s, 得到一组解,plow右移(或者high 左移)继续找另一组解移动指针了。如果前缀和数组满足递增数列,就只能暴力遍历o(n2)了

代码

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int> > res ;
        int plow =1, phigh = 2 ;
        while(plow < phigh)
        {
            int cur = (plow + phigh)*(phigh-plow+1)/2 ;
            if(cur == sum)
            {
                vector<int > 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<bit; i++)
            num = num >> 1 ;
        return (num & 1) == 1 ;
    }
    
    void FindNumsAppearOnce(vector<int> 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<size; i++)
        {
            res ^= data[i] ;
        }
        int bit = findFirst1(res) ;
        int n1 = 0, n2 = 0;
        for (int i=0; i< size; i++)
        {
            if (isBit1(data[i], bit))
            {
                n1 = n1^data[i]; 
            }
            else 
                n2 = n2^data[i] ;
        }
        *num1 =n1 , *num2 =n2 ;
    }
};

题目 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<int> data ,int k) {
        return biSearch(data, k+0.5) - biSearch(data, k-0.5) ;
    }
private:
    int biSearch(const vector<int> & 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<int> data) {
        if(data.size()<2) return 0;
        vector<int> copy;
        for(int i=0;i<data.size();i++){
            copy.push_back(data[i]);
        }
        long long count=InversePairsCore(data,copy,0,data.size()-1);
        copy.clear();
        return count%1000000007;
    }
     
    long long InversePairsCore(vector<int> &data,vector<int> &copy,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<int> 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] ;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值