2016.12.07开始刷题模式,掌握基础算法的同时学习巩固C++和Python编程基础,每道算法题我会尽量用两种语言去实现。目前是从剑指offer(牛客网)开始,欢迎同道中人共同学习,批评指正。本地测试代码和笔记会更新在我的github上Coding_Algorithms。
暂告一段落,忙其它的事,学习不止。
i++和++i的问题,解释下下面几段代码的输出
int main(void)
{
int i = 0;
cout << "Test execute order: " << endl
<< "cout: "
<< i << ", " << i++ << ", " << i++ + i++ << endl;
i = 0;
printf("printf(): %d, %d,%d\n", i,i++, i++ + i++);//3,2,0
//output:
/*Test execute order:
cout: 3, 2, 0
printf(): 3, 2,0 */
getchar();
}
int main(void)
{
int i = 0;
cout << "Test execute order: " << endl
<< "cout: "
<< i << ", " << ++i <<", " << ++i + ++i << endl;
i = 0;
printf("printf(): %d, %d,%d\n", i, ++i, ++i + ++i);//3,3,6
//output:
/*Test execute order:
cout: 3, 3, 6
printf(): 3, 3,6*/
getchar();
}
int main(void)
{
int i = 0;
printf("%d %d %d %d %d\n", i, i++, ++i, ++i + ++i, ++i + ++i + i++);//7 6 7 14 6
getchar();
}
新的解释:
对变量i的寄存器本身操作的时候(++i)会优先执行,然后拷贝本身寄存器值的时候会把上一个表达式的结果求出来。vs和g++区别就是vs不会用其他寄存器保存,而g++会保存。vs的第四项为14,就是++i + ++i执行,第三项++i,第二项i++,碰到第二项才本身寄存器拷贝,把结果求出来。g++执行第四项为什么会有中间寄存器保存?而g++第三项却没有,只要碰到除++i之外的运算符 都会保存到中间寄存器,这么解释?而VS只有碰到i++才会保存到中间寄存器?
下图是VS编译环境,第五项是三个++i,结果显然是i经过所有自增后的和。
这个问题还没怎么搞明白,特别是part3,在VS编译环境下输出是7 6 7 14 6,而在g++下编译结果是 7 6 7 10 6。下午跟同学看了汇编代码还是没怎么整明白,路过大神求指导!下图是g++反编译出来的。
来自好基友jack.j的手解汇编:POFEI_IS_SHIT的博客
之前只知道i++是先运算在赋值,++i是先赋值再运算,昨天学妹问了个问题把我给难住了(得好好补基础啊!)…如上面代码中所示,当i++与++i在同一表达式里进行运算的时候,各种情况各种输出结果。
下面几点是根据网友别总结的,没怎么看懂啊!
编译器决定对函数实参的表达式求解顺序,从右到左(看printf()的输出)。
只要出现i++,就会先对i作相关运算,然后再增1.
因为C语言函数参数入栈顺序是从右到左的,所以计算顺序是从右到左开始计算的;对于a++的结果,是有ebp寻址函数栈空间来记录中间结果的,在最后给printf压栈的时候,再从栈中把中间结果取出来;而对于++a的结果,则直接压寄存器变量,寄存器经过了所有的自增操作。
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode* pResult = NULL;//合并后链表的头指针
if (pHead1 == NULL)
return pHead2;//list1为空,直接返回list2
if (pHead2 == NULL)
return pHead1;//list2为空,直接返回list1
if (pHead1->val <= pHead2->val) {
pResult = pHead1;
pResult->next = Merge(pHead1->next, pHead2);//递归,两个链表中值小的头结点作为合并结点的下一结点
}
else {
pResult = pHead2;
pResult->next = Merge(pHead1, pHead2->next);
}
return pResult;
}
};
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode* pResult = NULL;
ListNode* current = NULL;
if (pHead1 == NULL)
return pHead2;
if (pHead2 == NULL)
return pHead1;
while (pHead1 != NULL && pHead2 != NULL) {
if (pHead1->val <= pHead2->val) {
if (pResult == NULL) {
current = pResult = pHead1;
}
else {
current->next = pHead1;
current = current->next;
}
pHead1 = pHead1->next;
}
else {
if (pResult == NULL) {
current = pResult = pHead2;
}
else {
current->next = pHead2;
current = current->next;
}
pHead2 = pHead2->next;
}
}
if (pHead1 == NULL) {
current->next = pHead2;
}
if (pHead2 == NULL) {
current->next = pHead1;
}
return pResult;
}
};
主要思路就是选取合并链表的下一节点,同步遍历两个链表,选择值比较小(相等取其一即可)的链表的当前结点作为合并链表的下一节点。可以通过递归和非递归实现,详见代码。
输入一个链表,反转链表后,输出链表的所有元素。
class Solution {
public:
ListNode* ReverseList(ListNode* pHead)
{
ListNode* pReversedHead = nullptr;//记录反转链表的头结点
ListNode* pNode = pHead;//记录当前遍历结点
ListNode* pPrev = nullptr;//记录上一结点
while (pNode != nullptr)
{
ListNode* pNext = pNode->next;
if (pNext == nullptr)
pReversedHead = pNode;
pNode->next = pPrev;
pPrev = pNode;
pNode = pNext;
}
return pReversedHead;
}
};
设置三个指针pReversedHead、pNode、pPrev分别指向反转链表的头结点
当前遍历结点、上一结点,只需遍历一次即可完成链表反转,在纸上画一下会比较清晰,详见代码。
输入一个链表,输出该链表中倒数第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 || k == 0)
return nullptr;
ListNode *pAhead = pListHead;//设置两个指针
ListNode *pBehind = nullptr;
for (unsigned int i = 0; i < k - 1; ++i)
{
if (pAhead->next != nullptr)
pAhead = pAhead->next;
else
{
return nullptr;
}
}
pBehind = pListHead;
while (pAhead->next != nullptr)
{
pAhead = pAhead->next;
pBehind = pBehind->next;
}
return pBehind;
}
};
要得到链表的倒数第K个结点,如果直接遍历一次得到链表长度n,然后再遍历到(n-k+1)可得到,但是显然这样的时间复杂度高,并不熟最优解。设置两个指针同时遍历,第一个指针先开始遍历,另一个指针不懂,等到第一个指针遍历到第k(此时走了k-1步)个结点的时候另一个指针再开始遍历(与第一个指针同步),这样等到第一个指针遍历到链表末尾时,第二个结点刚好到倒数第k个结点。
本地用例有构造链表及相关操作,见本地案例。
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
class Solution {
public:
void reOrderArray(vector<int> &array) {
vector<int> res;//新建临时数组,空间换时间
for(int i = 0; i < array.size(); i++)
{
if(array[i] & 1)//遍历,为奇数存入临时数组
res.push_back(array[i]);
}
for(int i = 0; i < array.size(); i++)
{
if((array[i] & 1) == 0)//遍历,为偶数存入临时数组
res.push_back(array[i]);
}
array = res;
}
};
# -*- coding:utf-8 -*-
class Solution:
def reOrderArray(self, array):
# write code here
odd = []
even = []
for i in array:
if i % 2 == 0:
even.append(i)
else:
odd.append(i)
return odd+even
这题关键在于时间复杂度,如果从头到尾遍历碰到奇数然后移动,这样的时间复杂度是O(n^2).所以考虑用临时空间,可以将时间复杂度降到O(2n),详见代码。
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
class Solution {
public:
double Power(double base, int exponent) {
if(exponent==0)//任何数的0次方都是1
return 1.0;
if(base-0.0<0.0000001 && base-0.0>-0.0000001)//如果base为0.0...返回0.0
return 0.0;
int exp;
if(exponent<0){//如果乘方数为负数,取负,结果取倒数
exp = -exponent;
}
else
exp = exponent;
double result =1.0;
while(exp>0){//循环累乘
result = result*base;
exp--;
}
if(exponent<0){//循环累除
result=1.0/result;
}
return result;
}
};
class Solution {
public:
double Power(double base, int exponent) {
if(exponent==0)
return 1;
if(exponent==1)
return base;
int exp,flag=0;
if(exponent<0){//如果乘方数为负数,取负,结果取倒数
exp = -exponent;
flag=1;
}
else
exp = exponent;
double result=Power(base,exp>>1);//右移,相当于除以2
result *=result;
if(exponent & 0x1 ==1)//取余判断奇数or偶数
result *= base;
if(flag==1)
return 1/result;
return result;
}
};
求数值的正数次方,思路不难,关键要考虑到几个特殊情况,0的0次方无意义,任何数的0次方都为1,指数为负数时要记得取倒数等。(详见代码及注释)
第二种高效解法并没想到…边看边理解。用右移运算代替除以2,用位与运算代替求余来判断奇偶。
假如输入的exponent为32,我们的目标是求出一个数的32次方,那么只要求出整数的16次方然后再取平方就行了,而16次方就是8次方的基础上取平方,依此类推…32次方只需要做5次乘法,显然提高了效率。
用公式表示:a(n)表示a的n次方
①当n为偶数时:a(n)=a(n/2)*a(n/2)
②当n为奇数时:a(n)=a((n-1)/2)*a((n-1)/2)*a
然后类似于斐波那契数列,递归解决。小插曲,本地测试的时候犯了个小错误,如下:
我把base定义成int型了,结果输出的时候,显示base是0.000000,但是结果却是正确的。无意中发现这个问题,于是跟好基友Zack.J.Dsg刷题小王子请教了一下。
解释如下:
我输入的base=2,64位double分为3段(下段解释),2的二进制0000…..00010,00000……..010的解释成double型就是一个很小的小数值可能是0.00000000000000000000000××,后面显示不出来了,但是参与运算是又把int型提升到double型了。63是符号位,62-52是指数部分…这个就需要看IEEE754标准了。
浮点标准用V=(-1)^s * M * 2 ^ E. M是小数部分的结果,E是指数部分的结果。根据指数部分(k代替)分为3种情况,k全为0,那个对应的十进制结果(e表示)就是0,E = 1 - Bias。Bias = 2 ^ (k - 1) - 1 double型就是1023
所以E=-1022,2^E=****(自己算好了)
小数部分(f表示)00000…..10。f=0.0000000….10(二进制小数),然后算出十进制就是M,就可以得到V的结果了
k全为1可以提供很大的数,k不全为0和不全为1是另一种情况,就不详细讲了。
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。比如9的二进制位1001,有2位是1.因此输出2。
class Solution {
public:
int NumberOf1(int n) {
int count=0;
unsigned int flag=1;
while(flag)
{
if(n & flag)//判断n的最低位是不是1->次低位是不是1->....
count++;
flag=flag<<1;//左移一位,1->2->4...
}
return count;
}
};
# -*- coding:utf-8 -*-
class Solution:
public:
int NumberOf1(int n) {
int count=0;
while(n)
{
++count;
n=(n-1)&n;//n与n-1位与运算,相当于把n最右边的1变成0,之后的全为0.如:1100&1011->1000
}
return count;
}
};
位运算不太熟悉,看了会书才做的。解题思路也是参考书上的,第二种方法有点难。书上还有一种罪初级的解法(n&1相当于n%2==1,但是位运算效率高多了),以此来判断末位是否为1;迭代,每次对n右移运算。这种方法碰到负数会陷入死循环。
第一种常规解法,依此判断n的最低位是否为1,次低位是否为1…
第二种解法有点开脑洞了,需要想到一个trick,一个整数(n)与这个整数减去一(n-1)做与运算相当于把整数最右边的1变成0.因此,有多少个1就执行多少次这个操作(见代码)。
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
class Solution {
public:
int rectCover(int number) {
if(number<1)
return 0;
int f = 1, g = 2;
while(number-->1) {
//循环迭代
//F[n]=F[n-1]+F[n-2](n>=2,F[1]=1,F[2]=2)
g += f;
f = g - f;
}
return f;
}
};
# -*- coding:utf-8 -*-
class Solution:
def rectCover(self, number):
# write code here
if number<1:
return 0
f = 1
g = 1
for i in range(number):
f,g = g,f+g
return f
思路在纸上画的,直接上图了,总之还是个斐波那契数列,归纳一下就好。
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
class Solution {
public:
int jumpFloorII(int number) {
return 1<<--number;
}
};
class Solution {
public:
int jumpFloorII(int number) {
if(number == 0)
return 0;
else if(number == 1)
return 1;
else
return 2*jumpFloorII(number-1);
}
};
class Solution {
public:
int jumpFloorII(int number) {
if(number <= 0) return 0;
if(number == 1) return 1;
int result = 1;
// 2^(n-1)
while(number-->1)
{
result *= 2;
}
return result;
}
};
# -*- coding:utf-8 -*-
class Solution:
def jumpFloorII(self, number):
# write code here
return 2 ** (number - 1)
从上一题的思路出发:对于第n个台阶来说,只能从n-1或者n-2的台阶跳上来(只有两种跳法),F(n) = F(n-1) + F(n-2);对于本题来说,对于第n个台阶有可能是从第n-1,n-2,n-3…1,0跳上来,因此可以归纳出:
f(0)=0
f(1)=1
f(2)=f(2-1)+f(2-2)
f(3)=f(3-1)+f(3-2)+f(3-3)
.
.
.
f(n-1)=f(n-2)+f(n-3)+…f(n-n) ①
f(n)=f(n-1)+f(n-2)+f(n-3)+…f(n-n) ②
②-①=f(n-1)
可以得出f(n)=2f(n-1)
这样问题就简单了。
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
class Solution {
public:
int jumpFloor(int number) {
int f = 1, g = 2;
while(number-->1) {
//循环迭代
//F[n]=F[n-1]+F[n-2](n>=2,F[1]=1,F[2]=2)
g += f;
f = g - f;
}
return f;
}
};
# -*- coding:utf-8 -*-
class Solution:
def jumpFloor(self, number):
# write code here
f = 1
g = 1
for i in range(number):
f,g = g,f+g
return f
对于第n个台阶来说,只能从n-1或者n-2的台阶跳上来,所以F(n) = F(n-1) + F(n-2)斐波拉契数序列
初始条件
n=1:只能一种方法
n=2:两种
斐波拉契数序列第七题已经写过了,这道题难点在于能不能看出这是一个斐波拉契数序列(只是初始条件改变了),嘿嘿,都是套路。后面还有一题变态跳台阶,关键还是在于问题思路转换。
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。 n<=39
class Solution {
public:
int Fibonacci(int n) {
int f = 0, g = 1;
while(n--) {
//循环迭代
//F[n]=F[n-1]+F[n-2](n>=2,F[0]=0,F[1]=1)
g += f;
f = g - f;
}
return f;
}
};
# -*- coding:utf-8 -*-
class Solution:
def Fibonacci(self, n):
# write code here
f = 0
g = 1
for i in range(n):
f,g = g,f+g
return f
斐波那契数列是个非常经典的问题了(
F[n]=F[n-1]+F[n-2](n>=2,F[0]=0,F[1]=1)
)。主要就是迭代(Iteration)VS 递归(Recursion),一般来说递归的代码比较简洁,但是因为递归是调用函数本身,函数调用是有时间和空间消耗的。迭代方法的时间复杂度为O(n),根据f(0)、f(1)可以计算出f(2),f(1)、f(2)可以计算出f(3)…以此类推循环遍历n次就可以计算出f(n)了。首先,每一次函数调用都需要在内存栈中分配空间保存参数,返回地址及临时变量,在入栈、出栈的过程中也会有时间消耗,因而实现的效率不如迭代循环。
其次,递归的思想是把一个大问题分成一个个小问题,这样造成很多 计算重复,因为这些小问题很可能发生重复现象。比如f(7)=f(6)+f(5);f(6)=f(5)+f(4);这里就发生重复计算了,因而影响效率。
此外,递归层数过多的话还会出现栈溢出,这是程序员最不想看到的吧。
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
class Solution {
public:
int MinInOrder(vector<int> num,int index1,int index2)
{
int res = num[index1];
for (int i = index1 + 1; i < index2;++i)//循环遍历,找到最小值
{
if (res > num[i])
res = num[i]; break;
}
return res;
}
int minNumberInRotateArray(vector<int> rotateArray) {
int len = rotateArray.size();
if (len <= 0)
return 0;
int index1 = 0;
int index2 = len - 1;
int indexMid = index1;//假如第一个值即为最小值,不会进入下面循环了,直接返回
while(rotateArray[index1]>=rotateArray[index2])
{
if (index2 - index1 == 1)
{
//如果两个指针的距离是1,说明第一个指针已经指向第一个递增序列的末尾,
//而第二个指针已经指向第二个递增子数组的开头。
indexMid = index2;//第二个递增数组的第一个数字即为最小值
break;
}
indexMid = (index1 + index2) / 2;
//如果下标为index1、index2和indexMid指向的三个数字都相等
//那么只能进行顺序查找
if (rotateArray[index1] == rotateArray[index2]&& rotateArray[index1]== rotateArray[indexMid])
{
return MinInOrder(rotateArray,index1,index2);
}
if (rotateArray[index1] <= rotateArray[indexMid])
index1 = indexMid;
else if (rotateArray[index2] >= rotateArray[indexMid])
index2 = indexMid;
}
return rotateArray[indexMid];
}
};
class Solution:
def minNumberInRotateArray(self, rotateArray):
# write code here
if len(rotateArray) == 0:
return 0
ret = rotateArray[0]
if len(rotateArray) == 1:
return ret
for i in range(1,len(rotateArray)):
now = rotateArray[i]
if now < ret:
ret = now
break
return ret
这道题要答得很好还是有点难得,最简单的思路就是遍历找最小值了,找到了就返回,见Python代码。这种方法效率比较低,时间复杂度为O(n),因此这道题的关键在于提高效率。
观察旋转数组的特点,其实可以分为两个递增数组,第二个数组的第一个元素即为最小值(咱叔还没有考虑特殊情况)。为了提高效率,采用二分查找的思想,时间复杂度提升到O(logn)。具体地,用两个指针p1、p2分别指向数组的开始与末尾,然后和二分查找一样取一个中间位置的值mid,分别与p1、p2所指的值比较,如果mid>=p1说明最小值还在mid的后面,然后把p1指向mid,如果mid<=p2说明最小值在mid前面或者就是它本身,然后把p2指向mid。如此循环,p1最终会指向第一个子序列的末尾,p2指向第二个子序列的开头,此时p2所在位置就是我们要找的最小值。每次查找,范围都会缩小一般,所以效率提高。
以上方法可以解决一般情况,但是题目没有说数组中的元素没有重复值,因此还要考虑这种情况。比如{1,0,1,1,1}、{1,1,1,0,1},它们的原始数组都为{0,1,1,1,1}。但是按照我们上面的思路,会发现p1、P2和mid的值都为1,这个时候就无法判断最小值是在mid之前还是之后了。所以针对这种特殊情况,还是要采用循环遍历解决。
还有数组已经排好序的情况,查找过程就不会进入循环,直接返回第一个值,详细见代码注释。
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
class Solution
{
public:
void push(int node) {
stack1.push(node);//压入第一个栈
}
int pop() {
if (stack2.empty()) {//如果stack2为空,这里保证了只有stack2完全为空的时候才会继续讲将stack1中元素压入到stack2
while (!stack1.empty()) {
int temp = stack1.top();//将stack1栈顶元素保存并压入stack2
stack2.push(temp);
stack1.pop();//删除stack1当前栈顶元素
}
}
if (stack2.empty()) {//经过上面的判断,如果stack2还为空说明队列中已经没有元素了。
printf("queue is empty");
return -1;
}
else{
int res = stack2.top();//去当前stack2栈顶元素作为pop输出
stack2.pop();//删除stack2栈顶元素,即新队列的top元素
return res;
}
}
class Solution:
def __init__(self):
self.stack1 = []
self.stack2 = []
def push(self, node):
# write code here
self.stack1.append(node)
def pop(self):
# return xx
if self.stack2 == []:
while self.stack1:
a = self.stack1.pop()
self.stack2.append(a)
return self.stack2.pop()
这题相对比较简单,知道栈先进后出,队列先进先出就差不多了。可以在纸上画一下,把抽象问题形象化。
stack1\stack2的出栈入栈顺序需要考虑周到,见代码注释。
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{ 1,2,4,7,3,5,6,8 }和中序遍历序列{ 4,7,2,1,5,3,8,6 },
则重建二叉树并返回。
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
if (pre.empty() || vin.empty())
return NULL;
return Construct(pre, 0, pre.size() - 1, vin, 0, vin.size() - 1);
}
TreeNode* Construct//构建递归函数划分左右子树
(
vector<int> pre, int pre_start, int pre_end, //前序遍历序列,序列开始位置,序列结束位置
vector<int> vin, int vin_start, int vin_end //中序遍历序列,序列开始位置,序列结束位置
)
{
TreeNode* root = new TreeNode(pre[pre_start]);//前序遍历的第一个元素为当前树的根结点
if (pre_start == pre_end)//如果当前前序遍历序列只剩下一个结点
{
if (vin_start == vin_end && pre[pre_start] == vin[vin_start])//如果中序遍历序列也只有一个结点并且与前序相同
return root;//返回当前唯一结点
//else//如果当前前序遍历序列只剩一个结点,但是中序遍历序列还有多个结点或者与前序遍历序列不同
//throw std::exception("Invalid input.");//说明输入的前序遍历序列跟中序遍历序列不匹配,输出异常
}
int i = vin_start;//在中序遍历虚列中找到根结点的位置
while (i < vin_end && vin[i] != root->val)
++i;
int leftlen = i - vin_start;//左子树长度
if (leftlen > 0)
root->left = Construct(pre, pre_start + 1, pre_start + leftlen, vin, vin_start, i - 1);
if (leftlen < pre_end - pre_start)
root->right = Construct(pre, pre_start + leftlen + 1, pre_end, vin, i + 1, vin_end);
return root;
}
};
前序遍历第一个数字就是根结点的值,然后根据中序遍历序列中根结点的位置就可以划分左右子树。然后再在划分后左右子树上递归地调用划分函数,去分别构建它们的左右子树,直至左右子树都不可再分为止。
实现该划分方法后还要考虑各种特殊情况,比如二叉树的根结点为空、输入的前序遍历序列与中序遍历序列不匹配等,考虑的越周到越好。
本地测试用例有点麻烦,需要自己构建二叉树结构以及相关的操作方法。
这题废了挺多时间,Python就不写了。
输入一个链表,从尾到头打印链表每个节点的值。
/**
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> v;
int a[10000];//新建一个数组存储链表数值
ListNode *p = head;
int i = 0;
while (p != NULL)
{
a[i++] = p->val;
p = p->next;
}
for (int j = i - 1; j >= 0; j--)//逆序输出
{
v.push_back(a[j]);
}
return(v);
}
};
#### 解答(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
l = []
head = listNode
while head:
l.insert(0, head.val)
head = head.next
return l
因为链表中的内存不是一次性分配的,因此想要得到第i个结点必须从头结点开始遍历,时间效率为O(n),而在数组中可以根据下标在O(1)时间内找到。
从前往后遍历链表,额外使用一个数组或者栈存储数值,然后输出即可。
本地测试用例有点麻烦,需要自己构建链表结构以及相关的操作方法(测试代码)。
请实现一个函数,将一个字符串中的空格替换成“ % 20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We % 20Are % 20Happy。
void replaceSpace(char *str, int length) {
if (str == nullptr && length <= 0)//字符串为空直接返回
return;
//str_len为字符串原始长度
int str_len = 0;
int space_num = 0;
int i = 0;
while (str[i] != '\0')//字符串以'\0'作为结尾符号O(n)
{
++str_len;//统计字符串原始长度
if (str[i] == ' ')
++space_num;//空格数
++i;
}
//new_len为替换后的字符串长度
int new_len = str_len + space_num * 2;
if (new_len > length)
return;
int p1 = str_len;//p1指向原始字符串末尾
int p2 = new_len;//p2指向新字符串末尾
while (p1 >= 0 && p2 > p1)//从后往前遍历O(n)
{
if (str[p1] == ' ')//p1指向的位置为空格
{
str[p2--] = '0';//p2填充替换字符串'%20'
str[p2--] = '2';
str[p2--] = '%';
}
else
{
str[p2--] = str[p1];
}
--p1;//继续向前遍历
}
}
# -*- coding:utf-8 -*-
class Solution:
# s 源字符串
def replaceSpace(self, s):
# write code here
r = []
for c in s:
if c == ' ':
r.append('%20')
else:
r.append(c)
return ''.join(r)
print Solution().replaceSpace('str cpy')
解题关键在于先遍历一遍字符串记录空格数,然后设置两个指针p1、p2分别指向旧字符串与新字符串的末尾,p1从后往前遍历,碰到空格就将p2所在的位置填充%20(依次往前填充)。这样所有的字符串都只需要进行依此复制即可,时间复杂度为O(n),注意一开始就遍历了一次字符串,因而实际复杂度为O(2n),详细见代码注释即可。
C/C++把常量字符串放到单独的一个内存区域,当几个指针赋值相同的常量字符串时,它们实际上指向相同的内存地址,但是用常量内存初始化数组时就不是这样了。
用python可以抖机灵.. return s.replace(’ ‘,’%20’)即可,这样就没有意义了。不过python可以直接定义一个新的list,然从开头开始遍历,直接append即可(详见代码),不用考虑边界。
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
bool Find(int target, vector<vector<int> > array) {
int rows, columns, x, y;
rows = array.size();//行数
columns = array[0].size();//列数
x = rows - 1; y = 0;//坐标定在左下角
while (x >=0 && y <=columns-1)
{
if (array[x][y] > target)
{
x--;//当前值大于目标值则上移
}
else if(array[x][y] < target)
{
y++;//当前值小于目标值则下移
}
else
{
return true;
}
}
return false;
}
class Solution:
# array 二维列表
def Find(self, target, array):
# write code here
rows = len(array)
columns = len(array[0])
x = rows - 1
y = 0
while x >=0 and y <=columns-1:
if array[x][y]>target:
x -= 1
elif array[x][y]1
else:
return True
return False
这道题思路倒不难,从左下角开始遍历,如果当前值大于目标值则上移,当前值小于目标值则下移,最坏时间复杂度为O(M+N),M×N的矩阵。
本地测试的时候vector数组初始化折腾了一会,之前没接触过vector,以为可以像C-style二维数组那样直接初始化,然而并不行。最后找到这个教程,实现了循环初始化,同时学习了vector的优点(安全,鲁棒)。首先,数组的越界可能会引起程序的崩溃,其次是动态性不好,包括动态改变大小,动态申请。
此外,编译遇到一些小问题,在VS2015中编译、运行成功的代码在g++编译时出错,问题出在nullptr。解决方法是g++编译命令需要加上C++11版本:
g++ -std=c++11 FindInPartiallySortedMatrix.cpp -o test
。
还有string Name值作为参数传入方法中(char* Name)会出警告warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]
,解决方法将参数改为:const char* testName
。