剑指offer|解析和答案(C++/Python) (一)
参考剑指offer(第二版),这里做一个学习汇总,包括解析及代码。代码均在牛客网进行验证(摘自自己的牛客网笔记)。
整个剑指offer解析链接:
剑指offer|解析和答案(C++/Python) (一).
剑指offer|解析和答案(C++/Python) (二).
剑指offer|解析和答案(C++/Python) (三).
剑指offer|解析和答案(C++/Python) (四).
剑指offer|解析和答案(C++/Python) (五)上.
剑指offer|解析和答案(C++/Python) (五)下.
剑指offer|解析和答案(C++/Python) (六).
习题
面试需要的基础知识
1.数组中重复的数字
2.二维数组中的查找
3.替换空格
4.从头到尾打印链表
5.重建二叉树
6.二叉树的下一个节点
7.用两个栈实现队列
8.斐波那契数列
9.旋转数组的最小数字
10.矩阵中的路径
11.机器人的运动范围
12.剪绳子
13.二进制中1的个数
14.跳台阶
1.数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路:
1.使用哈希表。将哈希表初始化为0,然后从头到尾扫描数组的每个数字,每扫到一个数字的时候,可以用O(1)的时间复杂度判断哈希表里面是否包含了该数字。如果有则加1。扫描完后,从头到尾扫描哈希表,只要对应的数值大于1,则有重复的数字,返回true。遍历完没有大于1的,则返回false。
代码:
C++
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
vector<int> completeNum;
for(int i = 0; i < length; ++i){
completeNum.push_back(0);
}
for(int i = 0; i < length; ++i){
++completeNum[numbers[i]];
}
for(int i = 0; i < length; ++i){
if(completeNum[i]>1){
*duplication = i;
return true;
}
}
return false;
}
};
2.使用哈希表的时间复杂度是O(n),空间复杂度是O(n),还有可以优化的空间。使用数值和ID交换的思路,可以实现空间复杂度是O(1)的算法。
数组数字的范围是0~n-1。如果数组中没有重复的数字,那么当数组排序之后数字i将出现在下标为i的位置,如果数组中有重复的数字,那么必定会出现数字i不仅仅出现在下标为i的位置,还可能出现在下标为m、x…的位置,只要发现下标为m和下标为i的数字相同,则发现有重复的数字,返回ture。
代码:
C++
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int numbers[], int length, int* duplication) {
for(int i = 0; i < length; ++i){
while(i != numbers[i]){
//判断
if(numbers[i] == numbers[numbers[i]]){
//找到重复的数字
*duplication = numbers[i];
return true;
}
//交换顺序
int temp = numbers[i];
numbers[i] = numbers[temp];
numbers[temp] = temp;
}
}
return false;
}
};
Python
# -*- coding:utf-8 -*-
class Solution:
# 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
# 函数返回True/False
def duplicate(self, numbers, duplication):
# write code here
for i in range(len(numbers)):
#对应位置上的元素不相等
while numbers[i] != i:
#判断
if numbers[i] == numbers[numbers[i]]:
#找到重复的数字
duplication[0] = numbers[i]
return True
#交换位置
temp = numbers[i]
numbers[i] = numbers[temp]
numbers[temp] = temp
#没有找到重复的
return False
2.二维数组中的查找
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:
从右上角元素为参考元素,每次和target进行比较。如果target等于参考元素,那么返回true,如果不是进行比较。1.target大于参考元素,则排除该行。2.如果target小于参考元素,则排除该列。一次类推,直到达到边界条件,如果还没有没有找到,则返回false。
C++
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
int cols = array[0].size();
int rows = array.size();
if( array.empty()==0 && cols > 0 && rows > 0){
//查找右上角的元素
int col = cols - 1;
int row = 0;
//如果target大于该元素,则排除该行,如果target小于该元素,排除该列
while( col >= 0 && row < rows){
if( target == array[row][col]){
return true;
}else if( target > array[row][col]){
++row;
}else if( target < array[row][col]){
--col;
}
}
}
return false;
}
};
Python
class Solution:
# array 二维列表
def Find(self, target, array):
# write code here
cols = len(array[0])
rows = len(array)
if cols > 0 and rows > 0:
col = cols - 1
row = 0
while row < rows and col >= 0 :
if target == array[row][col]:
return 1
elif target < array[row][col]:
col = col - 1
else :
row = row +1
return 0
3.替换空格
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路
两个方向
1.正向查找,找到’ ‘就把后面的字符数组后移2位,同时把’ ‘替换为’%20‘。但是这样每找到’ '就后把后面的所以字符数组移动,假设这个字符数组长度为n,时间复杂度为O(n^2)。
2.反向查找,对原字符数组进行“扩容”,计算所有的’ ‘的个数,一个指针指向“扩容”数组的最后一个,一个指针指向“原”数组的最后一个,依次赋值移动。
C++
class Solution{
public:
//length 为字符数组str的总容量
void replaceSpace(char*str,int length) {
//特殊情况判断
if( str == nullptr||length <= 0){
return;
}
//记录空格数目
int originalLength = 0;//计算原字符串实际长度
int numberOfBlank = 0;//记录空格数目
int i = 0;
while(str[i] !='\0'){
++originalLength;
if( str[i] ==' '){
++numberOfBlank;
}
++i;
}
//判断是否超出容量
int newLength = originalLength + 2*numberOfBlank;
if(newLength > length){
return;
}
int indexOfOriginal = originalLength;
int indexOfNew = newLength;//倒序排列赋值
while(indexOfOriginal >= 0 && indexOfNew > indexOfOriginal){
//注意originalLength是排除'\0'之后的长度,
//所以第一次进入循环时:str[originalLength] = '\0'
if( str[indexOfOriginal] ==' '){
str[indexOfNew--] ='0';
str[indexOfNew--] ='2';
str[indexOfNew--] ='%';
}else{
str[indexOfNew--] = str[indexOfOriginal];
}
--indexOfOriginal;
}
}
};
Python
class Solution:
# s 源字符串
def replaceSpace(self, s):
# write code here
originalLength = len(s)
#记录空格数
i = 0
numberOfBlank = 0
while i < originalLength:
if s[i] == ' ':
numberOfBlank = numberOfBlank + 1
i = i + 1
newLength = originalLength + 2*numberOfBlank
indexOriginal = originalLength - 1
indexNewstr = newLength - 1
#python的string类型不允许改变 新建一个string
new_string = [None for i in range(newLength)]
while indexOriginal >= 0 :
if s[indexOriginal] == ' ':
new_string[indexNewstr] = '0'
new_string[indexNewstr - 1] = '2'
new_string[indexNewstr - 2] = '%'
indexNewstr = indexNewstr - 3
else :
new_string[indexNewstr] = s[indexOriginal]
indexNewstr = indexNewstr - 1
indexOriginal = indexOriginal -1
return ''.join(new_string)
4.从头到尾打印链表
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
思路:
在不改变原来数据内容的前提下,进行从头到尾的打印链表内容。这是一个很明显的“后进先出”,可以使用“栈”这种数据结构进行存储。分两步走:1.遍历链表,存储链表的值到栈stack中。2.弹出栈stack顶层的元素到ArrayList中,直到栈为空。代码如下:
C++:
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
std::stack<ListNode*> nodes;//创建栈这种数据结构,后进先出。
ListNode* pNode = head;//先指向链表头结点
while(pNode != nullptr){//当没有指向最后的空指针时
nodes.push(pNode);//把该指针放进栈中
pNode = pNode->next;//指向下一个指针
}
vector<int> ArrayList;//存储打印的值
while(!nodes.empty()){//当栈不为空时
pNode = nodes.top();
ArrayList.push_back(pNode -> val);//放进向量中
nodes.pop();//弹出
}
return ArrayList;
}
};
Python
# -*- coding:utf-8 -*-
#class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 返回从尾部到头部的列表值序列,例如[1,2,3]
def printListFromTailToHead(self, listNode):
# write code here
stack = []#创建一个栈
pNode = listNode #创建一个结点遍历整个链表
while pNode:#当链表不是最后一个链表时
stack.append(pNode.val)
pNode = pNode.next
ArrayList = []#保存倒序输出的值
while len(stack) != 0:#栈不为空时
ArrayList.append(stack.pop())#弹出栈最后一个值
return ArrayList
5.重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
根据上图可以发现根节点的规律。前序的第一个节点便是根节点,根据这个节点可以差分左右子树,然后进一步遍历重建二叉树。
思路:
先根据前序遍历的第一个数字船舰根节点,并在中序遍历中找到根节点的位置,并进一步确定左右子树及其节点。通过上述步骤划分左右子树以及根节点之后,便递归调用函数实现二叉树重建。
C++:
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
TreeNode* tree;
if(pre.empty() || vin.empty()){
return nullptr;
}
//把vector转换成数组
int len = pre.size();
int preArr[len];
int vinArr[len];
for( int i = 0; i < len; i++){
preArr[i] = pre[i];
vinArr[i] = vin[i];
}
//使用递归
return Construct(preArr, preArr + pre.size() - 1,
vinArr, vinArr + vin.size() - 1);
}
TreeNode* Construct(int* startPre, int* endPre,
int* startVin, int* endVin){
//前序遍历的第一个数字是根节点值
int rootValue = startPre[0];
//这样写会越界
/*TreeNode* root;
root->val = rootValue;
root->left = nullptr;
root->right = nullptr;
*/
TreeNode* root = new TreeNode(rootValue);
cout<< "rootValue:"<<rootValue<<endl;
if(startPre == endPre){//只有一个节点时 前序和中序都只有一个值
if(startVin == endVin && *startPre == *startVin){
return root;
}else{
//throw std::exception("Invalid input");
cout<<"error"<<endl;
}
}
//在中序中找到根节点值的地址
int* rootInorder = startVin;//指针表明地址,数组的地址是连续的
while(rootInorder <= endVin && *rootInorder != rootValue){
++rootInorder;//地址+1 直到找到在中序中找到跟节点值
}
if(rootInorder == endVin && *rootInorder != *endVin){
//throw std::exception("Invalid input");
cout<<"error"<<endl;
}
int leftLength = rootInorder - startVin;//地址相减 得到左子树长度
int* leftPreEnd = startPre + leftLength;//在前序中找到左子树结束的地址
if(leftLength > 0){
//构建左子树
root->left = Construct(startPre + 1, leftPreEnd, startVin, rootInorder - 1);
}
if(leftLength < endPre - startPre){//表明除左子树外还有右子树
root->right = Construct(leftPreEnd + 1 , endPre, rootInorder + 1, endVin);
}
return root;
}
};
Python
class Solution:
def reConstructBinaryTree(self, pre, tin):
# write code here
if len(pre) == 0 or len(tin) == 0:
return None;
startPre = 0
endPre = len(pre) - 1
startTin = 0
endTin = len(tin) - 1
rootValue = pre[0] #前序第一个数字是根节点
root = TreeNode(rootValue)#创建根节点
#异常处理 不写这一部分也可以通过
if startPre == endPre:#当只剩一个节点时
if startTin == endTin and pre[startPre] == tin[startTin]:
return root
else:
print ("error")
#print "error"
#在中序中找到根节点对应的位置
rootTin = startTin
while rootTin <= endTin and tin[rootTin] != rootValue:
rootTin = rootTin + 1
#异常处理
if(rootTin == endTin and tin[rootTin] != rootValue):
print ("error")
#print "error"
#获取左子树的长度
leftLength = rootTin - startTin
#得到在前序排列中 左子树最后的地址序号
leftPreEnd = startPre + leftLength
#使用递归
#有左子树
if leftLength > 0 :
root.left = self.reConstructBinaryTree(pre[startPre + 1: leftPreEnd + 1],
tin[: rootTin])
#有右子树
if leftLength < endPre - startPre :
root.right = self.reConstructBinaryTree(pre[leftPreEnd + 1: ],
tin[rootTin +1 : ])
return root
6.二叉树的下一个节点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:
中序遍历为:{d, b, h, e, i, a, f,c, g}
1.存在右子树,下一个节点就是它右子树中最左节点。如b的下一个节点是h,a的下一个节点是f。
2.不存在右子树,本节点是父节点的左子节点,那父节点为下一个结点。如d的下一个节点是b,f的下一个节点是c。
3.不存在右子树,且本节点是父节点的右子节点,那沿着父结点向上遍历,直到找到一个节点是其父节点的左子节点。如节点b的父节点a就是节点i的下一个节点。由于节点a是树的跟节点,它没有父节点,因此节点g没有下一个节点。
代码:
C++
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode == nullptr){
return nullptr;
}
TreeLinkNode* pNext;
//1.存在右子树,下一个节点就是它右子树中最左节点
if(pNode->right != nullptr){
pNode = pNode->right;
while(pNode->left != nullptr){
pNode = pNode->left;
}
pNext = pNode;
}
//2.不存在右子树,本节点是父节点的左子节点,那父节点为下一个结点
//3.不存在右子树,且本节点是父节点的右节
//那沿着父结点向上遍历,直到找到一个节点是其父节点的左子节点
else if(pNode->next != nullptr){
TreeLinkNode* pCurrent;
TreeLinkNode* pParent;
pCurrent = pNode;
pParent = pNode->next;
while( pParent != nullptr&& pParent->left != pCurrent){
pCurrent = pParent;
pParent = pCurrent->next;
}
pNext = pParent;
}
return pNext;
}
};
Python
class Solution:
def GetNext(self, pNode):
# write code here
if pNode.right != None :
pNode = pNode.right
while pNode.left != None :
pNode = pNode.left
return pNode
elif pNode.next != None :
while pNode.next != None and pNode.next.left != pNode:
pNode = pNode.next
pNode = pNode.next
return pNode
elif pNode.next == None :
return None
7.用两个栈实现队列
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
思路:
根据题目要求要用栈(先入后出)实现队列(先入先出)的队列尾部插入和队列头部删除功能。
1.队列尾部插入。其实栈和队列是一致的,只需要输入即可。
2.队列头部删除。这是两种数据结构不一样的地方,栈是首先弹出的是栈顶(可以理解成队列的尾部)和队列首先弹出(删除)的是头部是不一样的(完全相反的)。换个思路想,如果把栈的顺寻进行颠倒,那么需要弹出的栈底,转换成另一个栈的栈顶不就好了么?
参考下列图示:
C++:
class Solution
{
public:
void push(int node) {
//放进元素
stack1.push(node);
}
int pop() {
//把stack1的元素放进stack2
if(stack2.size() <= 0){
while(stack1.size() > 0){
int data = stack1.top();
stack1.pop();
stack2.push(data);
}
}
int head = stack2.top();
stack2.pop();
return head;
}
private:
stack<int> stack1;
stack<int> stack2;
};
Python
# -*- coding:utf-8 -*-
class Solution:
def push(self, node):
# write code here
#栈1 先入
self.stack1.append(node)
def pop(self):
#判断栈2是否为空
if(len(self.stack2) == 0):
while len(self.stack1) > 0:
#把栈1放如栈2
self.stack2.append(self.stack1.pop())
#弹出栈顶
head = self.stack2.pop()
return head
# return xx
#初始化栈1 栈2
def __init__(self):
self.stack1 = []
self.stack2 = []
8.斐波那契数列
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。 n<=39
思路:
1.递归求解。这种方法简单,但是效率较低。
2.循环。这种算法复杂度为O(n),这种算法是从下往上计算的,首先根据f(0)和f(1)计算出f(2),再根据f(1)和f(2)计算出f(3)…以此类推就可以算出第n项了。
代码:
C++
class Solution {
public:
int Fibonacci(int n) {
if(n <= 1){
return n;
}
int firstFib;
int secondFib;
int num;
firstFib = 0;
secondFib = 1;
for(int i = 2; i <= n; i++){
num = firstFib + secondFib;
firstFib = secondFib;
secondFib = num;
}
return num;
}
};
Python
# -*- coding:utf-8 -*-
class Solution:
def Fibonacci(self, n):
# write code here
if n <= 1:
return n
firstFib = 0
secondFib = 1
num = 0
for i in range(1, n):
num = firstFib + secondFib
firstFib = secondFib
secondFib = num
return num
9.旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
思路:
参考图示
1.把P1指向数组的第一个元素,把P2指向数组的第二个元素。并求得P1、P2中间的元素。
2.在a图中P1、P2中间的元素是5,大于P1对应的元素(为3),把P1重新赋值为(P1+P2)/2。
3.重复计算,求得现在P1、P2中间的元素(为1)。
4.现在P1、P2中间的元素是1,小于P2(为2),把P2重新赋值为(P1+P2)/2。
5.现在P2 - P1 = 1,达到结束条件,P2即为翻转元素的最小值。
特殊情况:
1.翻转0个元素,返回第一个元素即可。
2.P1,P2和(P1+P2)/2对应的元素完全相等,无法判断。如数组{1,0,1,1,1}和数组{1,1,1,0,1}都可以堪称递增排序数组{0,1,1,1,1}的旋转。需要循环查找实现。
代码:
C++
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
if(rotateArray.empty()){
cout<<"Error: empty"<<endl;
}
int length = rotateArray.size();
int index1 = 0;//第一个指针
int index2 = length - 1; //第二个指针
int indexMid = index1;
while(rotateArray[index1] >= rotateArray[index2]){//排除有0个元素翻转
if(index2 - index1 == 1){
indexMid = index2;
break;
}
indexMid = (index1 + index2)/2;
if(rotateArray[indexMid] >= rotateArray[index1]){
index1 = indexMid;
}else if(rotateArray[indexMid] <= rotateArray[index2]){
index2 = indexMid;
}else if(rotateArray[indexMid] == rotateArray[index1]&&
rotateArray[indexMid] == rotateArray[index2]){//特殊情况 3个元素一样只能循环查找
indexMid = findMid(rotateArray, index1, index2);
break;
}
}
//如果翻转0个元素 直接返回第一个元素
return rotateArray[indexMid];
}
//在index1和index2之间循环查找
int findMid(vector<int> rotateArray, int index1, int index2){
int index = index1;
for(int i = index1 + 1; i < index2; ++i){
if(rotateArray[index] > rotateArray[i]){
index = i;
break;
}
}
return index;
}
};
Python
# -*- coding:utf-8 -*-
class Solution:
def minNumberInRotateArray(self, rotateArray):
# write code here
index1 = 0
index2 = len(rotateArray) - 1#rotateArray是list 要用len()得到其长度
indexMid = index1
while rotateArray[index1] >= rotateArray[index2]:
if index2 - index1 == 1:
indexMid = index2
break
indexMid = (index1 + index2)/2
if rotateArray[indexMid] == rotateArray[index1] and rotateArray[indexMid] == rotateArray[index2]:
indexMid = self.findMid(rotateArray, index1, index2)
break
elif rotateArray[indexMid] >= rotateArray[index1]:
index1 = indexMid
elif rotateArray[indexMid] <= rotateArray[index2]:
index2 = indexMid
return rotateArray[indexMid]
def findMid(self, rotateArray, index1, index2):
index = index1
for i in range(index1 + 1, index2):
if rotateArray[index] > rotateArray[i]:
index = i
break
return index
10.矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。
例如
a b c e
s f c s
a d e e
矩阵中包含一条字符串"bccced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
思路:
从矩阵中的任意一个点出发,如果这个点是寻找元素中的一个,那么就从这个点四周继续寻找。如果四周存在寻找元素中的一个,那么就从那个元素四周继续寻找,如果四周没有符合的元素,回溯到上一个点继续寻找。
代码:
C++:
class Solution {
public:
bool hasPath(char* matrix, int rows, int cols, char* str)
{
if(matrix == nullptr|| rows < 1 || cols < 1 || str == nullptr){
return nullptr;
}
//创建一个数组 保存矩阵路径是否被选取
bool *visited = new bool[rows * cols];
for(int i = 0; i< rows * cols; i++){
visited[i] = false;
}
//定义一个被查找的路径的长度
int pathLength = 0;
for(int row = 0; row < rows; row++){
for(int col = 0; col < cols; col++){
if(hasPathCore(matrix, rows, cols, row, col, str, pathLength,visited)){
return true;
}
}
}
delete[] visited;
return false;
}
bool hasPathCore(const char* matrix, int rows, int cols, int row,
int col, const char* str, int& pathLength, bool* visited){
if(str[pathLength] == '\0'){//'\0'是str的结束符 表明每个字符都有匹配的路径
return true;
}
bool hasPath = false;
if(row >= 0&& row < rows && col >= 0 && col < cols &&//在矩阵范围内部
!visited[row * cols + col]&&//这个格子没有路过
matrix[row * cols + col] == str[pathLength]//字符串中的字符等于矩阵该位置对应的元素
){
++pathLength;//找下一个字符
visited[row * cols + col] = true;//对应位置下的格子被占据
//回溯法
//接下来找相邻四个位置下是否存在对应的元素
hasPath = hasPathCore(matrix, rows, cols, row - 1, col, str, pathLength,visited)||
hasPathCore(matrix, rows, cols, row, col - 1, str, pathLength,visited)||
hasPathCore(matrix, rows, cols, row + 1, col, str, pathLength,visited)||
hasPathCore(matrix, rows, cols, row, col + 1, str, pathLength,visited);
if(!hasPath){//如果没有存在的路径
--pathLength;//回溯到上一个字符
visited[row * cols + col] = false;
}
}
return hasPath;
}
};
Python
# -*- coding:utf-8 -*-
class Solution:
def hasPath(self, matrix, rows, cols, path):
# write code here
#初始化 判断路径是否被占据
visited = [False] * rows * cols
#以矩阵每一个点为初始点尝试寻找路径是否存在
for row in range(0, rows):
for col in range(0, cols):
if self.hasPathCore(matrix, rows, cols, path, row, col, visited):
return True
return False
def hasPathCore(self, matrix, rows, cols, path, row, col, visited):
if not path:
return True
#默认没有找到
hasPath = False
#查找这个点是否满足条件
if row >= 0 and row < rows and col >= 0 and col < cols and \
visited[row * cols + col] == False and \
path[0] == matrix[row * cols + col]:#矩阵范围内 格子未被占据 对应元素相等
#格子被占据
visited[row * cols + col] = True
#在四周判断是否存在该元素
hasPath = self.hasPathCore(matrix, rows, cols, path[1:], row - 1, col, visited) or\
self.hasPathCore(matrix, rows, cols, path[1:], row + 1, col, visited) or\
self.hasPathCore(matrix, rows, cols, path[1:], row, col - 1, visited) or\
self.hasPathCore(matrix, rows, cols, path[1:], row, col + 1, visited)
#如果没有找到 回溯到上一个元素
if hasPath == False:
visited[row * cols + col] = False
return hasPath
11.机器人的运动范围
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
思路:
使用回溯法解决。方格可以看作m*n的矩阵。除边界上的格子,其他格子都有4个相邻的格子。机器人从(0,0)开始移动,移动到(i,j)时判断是否能够进入,能进入再判断四周能否进入,依此类推。
代码:
C++
class Solution {
public:
int movingCount(int threshold, int rows, int cols)
{
//创建一个数组 判断路径是否走过
bool *visited = new bool[rows * cols];
for(int i = 0; i< rows * cols; i++){
visited[i] = false;
}
int countNum = 0;
//从(0,0)开始走
countNum = movingCountCore(threshold, rows, cols, 0, 0, visited);
delete[] visited;
return countNum;
}
int movingCountCore(int threshold, int rows, int cols, int row, int col, bool* visited){
int countNum = 0;
if(row >= 0 && row < rows && col >= 0 && col < cols &&//区域之内
visited[row * cols + col]== false && //没有走过这个格子
getNum(row) + getNum(col) <=threshold){//小于阈值
visited[row * cols + col] = true;
countNum = 1 + movingCountCore(threshold, rows, cols, row + 1, col, visited)
+ movingCountCore(threshold, rows, cols, row - 1, col, visited)
+ movingCountCore(threshold, rows, cols, row, col + 1, visited)
+ movingCountCore(threshold, rows, cols, row, col - 1, visited);
}
return countNum;
}
int getNum(int num){
int sum = 0;
while(num > 0){
sum += num%10;
num = num/10;
}
return sum;
}
};
Python
# -*- coding:utf-8 -*-
class Solution:
def movingCount(self, threshold, rows, cols):
# write code here
#建立数组保存是否经过路径
visited = [False] * rows * cols
count = 0
count = self.movingCountCore(threshold, rows, cols, 0, 0, visited)
return count
def movingCountCore(self,threshold, rows, cols, row, col, visited):
count = 0
if row >= 0 and row < rows and col >= 0 and col < cols and \
visited[row * cols + col] == False and \
self.getNum(row) + self.getNum(col) <= threshold:
visited[row * cols + col] = True
count = 1 + self.movingCountCore(threshold, rows, cols, row - 1, col, visited) \
+ self.movingCountCore(threshold, rows, cols, row + 1, col, visited) \
+ self.movingCountCore(threshold, rows, cols, row, col - 1, visited) \
+ self.movingCountCore(threshold, rows, cols, row, col + 1, visited)
return count
def getNum(self,num):
sumNum = 0
while num > 0:
sumNum = sumNum + num % 10
num = num /10
return sumNum
12.剪绳子
给你一根长度为n的绳子,请把绳子剪成m段(m、n都是整数,n>1并且m>1,每根绳子的长度记为k[0],k[1],…,k[m])。请问k[0]xk[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18.
思路:
1.动态规划。O(n2)时间复杂度,O(n)空间复杂度。
在剪一刀的时候,有n-1种选择。剪出的长度可能分别为1,2,…,n - 1。因此f(n) = max(f(i) x f(n - i)),0<i<n。
从下往上,先得到f(2)、f(3),再得到f(4)、f(5),直到f(n)。
子问题的最优解存储在数组products中。数组中第i个元素表示把长度为i的绳子剪成若干段之后各长度乘积的最大值,即
f
(
i
)
f(i)
f(i)。在求
f
(
i
)
f(i)
f(i)之前,对于每一个j( 0 < i < j),
f
(
i
)
f(i)
f(i)都解出来,并放在projects[j]里。为求解
f
(
i
)
f(i)
f(i),需要求解所有
f
(
j
)
×
f
(
i
−
j
)
f(j) \times f(i-j)
f(j)×f(i−j)并比较它们的最大值。
参考代码:
class Solution {
public:
int cutRope(int number){
if(number < 2)
return 0;
if(number == 2)
return 1;
if(number == 3)
return 2;
int* products = new int[number + 1];
products[0] = 0;
products[1] = 1;
products[2] = 2;
products[3] = 3;
int maxNum = 0;
for(int i = 4; i <= number; i++){
maxNum = 0;
for(int j = 1; j <= i/2; ++j){
int product = products[j] * products[i - j];
if(maxNum < product){
maxNum = product;
}
products[i] = maxNum;
}
}
maxNum = products[number];
delete[] products;
return maxNum;
};
};
# -*- coding:utf-8 -*-
class Solution:
def cutRope(self, number):
# write code here
if number < 2:
return 0
if number == 2:
return 1
if number == 3:
return 2
products = []
for i in range(4):
products.append(i)
for i in range(4, number + 1):
products.append(0)
for i in range(4, number + 1):
maxNum = 0
for j in range(1, i/2 + 1):
product = products[j] * products[i - j]
if maxNum < product:
maxNum = product
products[i] = maxNum
maxNum = products[number]
return maxNum
2.贪婪算法
当n >= 5 时,尽可能多地剪长度为3的绳子;当剩下的绳子长度为4时,把绳子剪成2段长度为2的绳子。
贪婪算法需要数学证明思路的正确性。
当n >= 5 时,可证2(n - 2)>n,并且3(n - 3)>n。即当n >= 5 时,尽可能多地剪长度为3或2的绳子。又由于当n >= 5 时,3(n - 3) > 2(n - 2)。因此尽可能选择剪长度为3的绳子。当长度为4时,13 < 22。
参考代码:
class Solution {
public:
int cutRope(int length){
if(length < 2)
return 0;
if(length == 2)
return 1;
if(length == 3)
return 2;
//尽可能多地减去长度为3的绳子段
int timesOf3 = length/3;
//当绳子最后剩下的长度为4的时候,不能减去长度为3的绳子段。
//此时更好的办法是将绳子剪成长度为2的两端,因为2x2 > 1x3
if(length - timesOf3 * 3 == 1){
timesOf3 = timesOf3 - 1;
}
int timesOf2 = (length - timesOf3 * 3)/2;
return (int)(pow(3, timesOf3)) * (int)(pow(2, timesOf2));
}
};
Python
# -*- coding:utf-8 -*-
class Solution:
def cutRope(self, number):
# write code here
if number < 2:
return 0
if number == 2:
return 1
if number == 3:
return 2
timesOf3 = number//3
if number - timesOf3*3 == 1:
timesOf3 -= 1
timesOf2 =(number - timesOf3*3)//2
return 3**timesOf3 * 2**timesOf2
13.二进制中1的个数
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
思路:
1.常见思路。
避免死循环,不右移输入数字n。首先把n和1做与运算,进行判断最低位是不是1。接着把1左移成2,进行判断,依此类推…
代码
C++
class Solution {
public:
int NumberOf1(int n) {
unsigned int num = 1;
int countNum = 0;
while(num){
if(num & n){
++countNum;
}
num = num << 1;
}
return countNum;
}
};
Python
**使用Python的时候需要注意,和C++通过移位来使得数值溢出是不可行的,会造成无限死循环。因此需要直接对其判断。**选择0xffffffff是因为在C++中int占4个字节共32位。
class Solution:
def NumberOf1(self, n):
# write code here
numFlag = 1
count = 0
while numFlag & 0xffffffff != 0 :
if numFlag & n :
count = count + 1
numFlag = numFlag << 1
return count
2.技巧性方法。**把一个整数减去1,再和原整数做与操作,会把该整数最右边的1变成0。**如:1100,减去1后成1011,再和1100做与操作,变成1000,即最右边的1变成0。能做多少次这个操作就说明有多少个1。
代码:
C++
class Solution {
public:
int NumberOf1(int n) {
int countNum = 0;
while(n){
++countNum;
n = (n - 1) & n;
}
return countNum;
}
};
Python
在 Python 程序中,当对一个负整数与其减 1 后的值按位求与,若结果为 0 退出,循环执行此过程。由于整型数可以有无限的数值精度,其结果永远不会是 0,如此编程,在 Python 中,只会造成死循环。同样使用0xffffffff限定范围。
class Solution:
def NumberOf1(self, n):
# write code here
count = 0
while n & 0xffffffff != 0 :
count = count + 1
n = (n - 1) & n
return count
14.跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
思路:
先考虑最简单的情况。如果只有1级台阶,那显然只有一种跳法。如果有2级台阶,有两种跳法:1.分两次跳,每次跳一级。2.一次跳,每次次跳两级。
一般情况:把n级台阶时的跳法看成n的函数,记为
f
(
n
)
f(n)
f(n)。当n>2时,第一次跳的时候就两种不同选择:
1.第一次只跳1级,此时跳法数目等于剩下的n - 1级台阶的跳法数目,即为
f
(
n
−
1
)
f(n-1)
f(n−1)。
2.第一次只跳2级,此时跳法数目等于剩下的n - 2级台阶的跳法数目,即为
f
(
n
−
2
)
f(n-2)
f(n−2)。
此时n级台阶的不同跳法的总数为
f
(
n
)
=
f
(
n
−
1
)
+
f
(
n
−
2
)
f(n)=f(n-1)+f(n-2)
f(n)=f(n−1)+f(n−2)
代码:
C++
class Solution {
public:
int jumpFloor(int number) {
if(number < 3)
return number ;
int floorOne = 2;
int floorTwo = 1;
int floorN = 0;
for(int i = 3; i <= number; ++i){
floorN = floorOne + floorTwo;
floorTwo = floorOne;
floorOne = floorN;
}
return floorN;
}
};
Python
# -*- coding:utf-8 -*-
class Solution:
def jumpFloor(self, number):
# write code here
if number < 3:
return number
floorOne = 2
floorTwo = 1
for i in range(3, number + 1):
floorN = floorOne + floorTwo
floorTwo = floorOne
floorOne = floorN
return floorN