剑指offer(11---20)

11、二进制中1的个数

输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。

一般拿到这种问题,我们首先想到的算法就是判断最低位是不是1,也就是将这个数与1做与运算,看是否是1,如果是1,个数+1,然后这个数右移一位,进行循环直到这个数为0,但是这种方法在遇到负数的情况下,很容易造成死循环的问题,因此我们要考虑另一种算法,也就是我们不将这个数右移,而是每一次将1左移一位,直到1变成了0这样就可以判断这个数的最低位、次低位、次次低位等是不是1,但是这种算法比较常规,我们还可以再想想第三种算法,即:
我们拿到这个数,给它-1,如果它的最低位是1,那么此时变为0,此时这个数和-1之后的进行与操作,count++,然后继续此过程直到这个数为0,就可以得到它的二进制有多少个1;
如果这个数的第n位是1,右边的位都是0,那么-1之后第n位变为0,第n位右边的位全部变为1,左边的不变,这样我们把这个整数和它-1之后的数进行与运算,-1之后的数右边的就可以全部变为0,循环直到这个数为0,就可以得到这个数二进制1的个数。
实现代码(第二种算法):

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

第三种算法:

class Solution {
public:
     int  NumberOf1(int n) {
         int count=0;
         while(n)
         {
             count++;
             n=n&(n-1);
         }
         return count;
         
     }
};
12、数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0.

此时我们如果题目已经设置了exponent是无符号整数,此时我们可以采用递归的方式,即例如exponent是32时,只需要知道它的16次方,再平方一次就可以了;而要知道16次方,只需要知道8次方,再平方一次就可以了;而要知道8次方,只需要知道4次方,再平方一次就可以了;要知道4次方,只需要知道2次方,再平方一次就可以了;要知道2次方,只需要知道1次方,再平方一次就可以了,如果exponent是奇数,最后只需要再乘一遍base即可;那么此时它的代码就是:

double Power(double base, unsigned int exponent)
{
	if(exponent==0)
		return 1;
	if(exponent==1)
		return base;
	double sum=Power(base,exponent>>1);
	sum*=sum;
	if(exponent & 0x1 ==1)//是奇数
		sum*=base;
	return sum;
}

但是我们要注意此题给的是exponet是int类型的整数,也就是有符号整数,那么就要考虑负数的情况,就不能使用递归的方式了,此时我们需要判断exponent如果是负数,最终的sum=1/sum;
实现代码:

class Solution {
public:
    double Power(double base, int exponent) {
        if(base == 0)
            return 0;
        if(exponent == 0)
            return 1;
        if(exponent == 1)
            return base;
        int e=exponent;
        if(exponent < 0)
        {
            e = -exponent;
        }
        //e为exponent的绝对值
        double sum=1;
        while(e!=0)
        {
            sum*=base;
            e--;
        }
        if(exponent < 0)
            sum=1/sum;
        return sum;
    
    }
};
13、调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

这个题拿到的第一想法就是进行循环遍历,如果遇到当前元素是偶数,下一个元素是奇数,就交换,一共遍历arr.size()次,并且这种方法也保证了相对位置不变,实现代码:

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

如果题目没有要求相对位置不变的话,我们可以采用begin指针从前往后遍历数组找到第一个偶数,同样采用end指针从后往前遍历找到第一个奇数,如果begin<end,交换,然后继续循环直到begin>=end即可,实现代码:

void reOrderArray(vector<int> &array)
{
	int begin=0;
	int end=array.size()-1;
	while(begin<end)
	{
		while(begin<end && (array[begin] & 0x1!=0))//找到第一个不是奇数的
			begin++;
		while(begin<end && (array[end] & 0x1 == 0))//找到第一个不是偶数的
			end--;
		if(begin<end)
		{
			int t=array[begin];
			array[begin]=array[end];
			array[end]=t;
		}
	}
}

我们这里要扩展的是,如果我们遇到此类问题,比如所有负数都在非负数的前面,只需要第二个和第三个while中判断条件,因此我们可以将其写成一个函数,利用函数指针的方式即可。

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

输入一个链表,输出该链表中倒数第k个结点。
我们这个算法的思想就是可以先将一个指针走k个位置,然后另一个指针开始一起走剩下的位置,最后返回第二个指针所在的结点即可(利用前后指针)
实现代码:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead==nullptr)
            return nullptr;
        ListNode *front=pListHead;
        ListNode *back=pListHead;
        for(int i=0;i<k;++i)
        {
            if(front==nullptr)
                return nullptr;
            front=front->next;
        }
        while(front!=nullptr)
        {
            front=front->next;
            back=back->next;
        }
        return back;
    }
};
15、反转链表

输入一个链表,反转链表后,输出新链表的表头。

我们的思想就是先保存pHead后所有的结点,然后将pHead->next置为nullptr,再将保存的结点逐个头插到pHead之前即可。

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(pHead == nullptr)
            return nullptr;
        ListNode *p= pHead->next;
        pHead->next=nullptr;
        while(p)
        {
            ListNode *next=p->next;
            p->next=pHead;
            pHead=p;
            p=next;
        }
        return pHead;
    }
};
16、合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

有两种方法,第一种就是定义一个结果链表,分别遍历两个链表,每次将两者较小的尾插到结果链表中(记得要考虑两个链表不一样长的情况,最后要将长的链表剩下的结点链接到结果链表后),实现代码:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1==nullptr)
            return pHead2;
        if(pHead2==nullptr)
            return pHead1;
        ListNode* result=nullptr;
        ListNode* c1=pHead1;
        ListNode* c2=pHead2;
        ListNode* last=nullptr;
        while(c1!=nullptr && c2!=nullptr)
        {
            if(c1->val<=c2->val)
            {
                ListNode* next=c1->next;
                c1->next=nullptr;//将c1拆下来
                if(result == nullptr)
                    result=c1;
                else
                    last->next=c1;
                last=c1;
                c1=next;
            }
            else
            {
                ListNode* next=c2->next;
                c2->next=nullptr;//将c2拆下来
                if(result==nullptr)
                    result=c2;
                else
                    last->next=c2;
                last=c2;
                c2=next;
            }
        }
        if(c1!=nullptr)
            last->next=c1;
        if(c2!=nullptr)
            last->next=c2;
        return result;
    }
};

当然第二种方法就是使用递归的方式,如果pHead1小于pHead2,那么此时result=pHead1,result的next就继续递归即可,反之一样。

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1==nullptr)
            return pHead2;
        if(pHead2==nullptr)
            return pHead1;
        ListNode* result=nullptr;
        if(pHead1->val<=pHead2->val)
        {
            result=pHead1;
            result->next=Merge(pHead1->next,pHead2);
        }
        else
        {
            result=pHead2;
            result->next=Merge(pHead1,pHead2->next);
        }
        return result;
    }
};
17、树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

判断B是否是A的子结构,首先判断两者的根结点的值是否相同,如果相同,则继续判断两者是否具有相同的结构,如果相同,返回true,如果两者根结点的值不相同,则要在A的左子树中递归,找是否有值和B的根结点值相同的,看是否结构相同,否则去A的右子树中递归

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
private:
    bool isSame(TreeNode *p,TreeNode *q)//判断以p为根结点与以q为根结点的二叉树是否相同
    {
        if(q==nullptr)
            return true;
        if(p==nullptr)
            return false;
        if(p->val!=q->val)
            return false;
        return isSame(p->left,q->left) && isSame(p->right,q->right);
    }
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        if(pRoot1==nullptr || pRoot2==nullptr)
            return false;
        if(pRoot1->val==pRoot2->val)//如果两个根结点的值相同,则判断这两个代表的二叉树是否相同,相同返回true
        {
            if(isSame(pRoot1,pRoot2))
                return true;
        }
        //如果两个根结点不相同,继续去pRoot1的左子树中找是否相同,找不到去右子树中找
        return HasSubtree(pRoot1->left,pRoot2) || HasSubtree(pRoot1->right,pRoot2);
    }
};
18、二叉树的镜像

操作给定的二叉树,将其变换为源二叉树的镜像。

要得到二叉树的镜像,交换当前根结点的左右孩子,然后左右子树继续递归即可
实现代码:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot==nullptr)
            return;
        TreeNode *t = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = t;
        Mirror(pRoot->left);
        Mirror(pRoot->right);
    }
};
19、顺时针打印矩阵

即例如:
在这里插入图片描述

我们假设start表示左上角的坐标,result为最终结果数组,row为矩阵行数,col为矩阵列数
首先我们要知道如果想要从左到右走(第一步),不需要条件就可以,即如图这种:
在这里插入图片描述
此时只需要实现代码:

for(int i=start;i<=col-1-start;++i)
{
	result.push_back(matrix[start][i]);
}

如果我们要从上往下走(第二步),就要保证有的走,即最少要再有一行,即一共两行,也就是条件是终止行数>起始行数,此时第start行的第col-1-start列的数已经走过,向下走就不用再走一遍,循环从start+1开始,如图:
在这里插入图片描述
实现代码:

if(start<row-1-start)
{
	for(int i=start+1;i<=row-1-start;++i)
	{
		result.push_back(matrix[i][col-1-start]);
	}
}

如果我们要从右往左走(第三步),也要保证有的走,即最少要再有一列,即一共两列,条件就是在第二步的终止行数大于终止列数前提下,终止列数大于起始列数,此时第col-1-start列的第row-1-start这一行的数第二步已经走过,这里就不用走了,也就是循环从col-1-1-start开始,如图:
在这里插入图片描述
实现代码:

if(start<row-1-start && start<col-1-start)
{
	for(int i=col-1-1-start;i>=start;--i)
	{
		result.push_back(matrix[row-1-start][i]);
	}
}

我们要从下往上走(第四步),也要保证有的走,即要再有一行可以走,也就是要在第三步的前提下,再有一行,即三行两列,也就是终止行数要比起始行数至少大于2,终止列数大于起始列数,第三步已经将第row-1-start行的第start列的那个数走过来和第一步已经将第start行的第start列那个数走过了,因此循环从row-1-1-start开始,终止于start+1,如图为要满足四步所得,即三行两列:
在这里插入图片描述
第四步的代码为:

if(start<row-1-1-start && start<col-1-start)
{
	for(int i=row-1-1-start;i>=start+1;--i)
	{
		result.push_back(matrix[i][start]);
	}
}

最后就是我们的循环条件,我们可以得出,走最后一圈的左上角的坐标为(2,2),也就是循环条件为row>start2和col>start2,表示走了start圈,总体实现代码:

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        vector<int> result;
        int row=matrix.size();
        int col=matrix[0].size();
        if(matrix.size() == 0 || row==0 || col==0)
            return result;
        int start=0;
        while(row > start*2 && col > start*2)
        {
            //第一步
            for(int i=start;i<=col-1-start;++i)
            {
                result.push_back(matrix[start][i]);
            }
            //第二步
            if(start<row-1-start)
            {
                for(int i=start+1;i<=row-1-start;++i)
                {
                    result.push_back(matrix[i][col-1-start]);
                }
            }
            //第三步
            if(start<row-1-start && start<col-1-start)
            {
                for(int i=col-1-1-start;i>=start;--i)
                {
                    result.push_back(matrix[row-1-start][i]);
                }
            }
            //第四步
            if(start<row-1-1-start && start<col-1-start)
            {
                for(int i=row-1-1-start;i>=start+1;--i)
                {
                    result.push_back(matrix[i][start]);
                }
            }
            start++;
        }
        return result;
    }
};
20、包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法.

这里我们使用两个栈,in栈保存每一次压入的数,out的栈顶一定是最小的,也就是out如果是空的或者要压入的数小于out的栈顶元素,就压入out栈,否则out压入自己当前的栈顶元素。
实现代码:

class Solution {
private:
    std::stack<int> in;
    std::stack<int> out;
public:
    void push(int value) {
        in.push(value);
        if(out.empty() || value<=out.top())
            out.push(value);
        else
            out.push(out.top());
    }
    void pop() {
        in.pop();
        out.pop();
    }
    int top() {
        return in.top();
    }
    int min() {
        return out.top();
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值