17_7_14:逆置单链表+查找单链表的倒数第K个节点+非常规方法实现Add函数
2017-07-14 20:39
399 查看
1.【基础题】–逆置/反转单链表+查找单链表的倒数第k个节点,要求只能遍历一次链表
2.【附加题】–实现一个Add函数,让两个数相加,但是不能使用+、-、*、/等四则运算符。ps:也不能用++、–等等
**
**
(1)利用两数相加,从后往前进位的思想
思路:
1,0和1可以表示某一位上的数值。(对于sum来说)
2,0和1还可以表示两种状态,即序列中某一位的状态(不需要进位与需要进位)。(对于carry来说)
3,a^b运算的结果是:二进制序列,不进位,只相加的结果sum。(0^0=0,1^0=1,1^1=0)
4,a&b运算,然后左移一位,的结果是:二进制序列中哪些位是需要进位carry。(0&0=0,1&0=0,1&1=1,然后将序列左移1位)
5,通过sum与carry带入3,4中的a与b,可以从后往前不断进位,直到carry为0,表示没有需要进位的位。
(2)利用数组来计算。
这种办法非常巧妙
思路:
1,数组通过下标的偏移,可以计算出基于首元素地址的某一元素位置,从而访问该元素。
那么我们可以利用编译器的这种行为,来让编译器自动帮我们进行加法运算。
2,32位下, int值与指针的sizeof大小都是4字节。char的大小为1字节。
所以,可以将两数a和b中。a强转为char*类型地址c。这样,b用来表示下标,计算c[b]时,是以1字节为单位计算偏移的。
*/
由于这种方法是从别处了解,本人第一次见,所以需要仔细分析分析
1,只是用于32平台?
是的,因为通过int值与指针值在32位平台下,都是4个字节,大小一样。所以在解析时,可以通过(char *) 、(int)来相互强转,而不会,多读取或者少读取字节数据。
2,b为负数时,可满足情况?
可以满足。因为,通过数组下标来访问元素的本质,就是基于某个地址的偏移。可以有正偏移(往后偏移),也可以有负偏移(往前偏移)。
测试用例:
3,a为负数时,可满足情况吗?
可以满足。因为,在将a值转化为地址c时,会将a的补码序列,看做无符号数。我担心的是,在将有符号数化作无符号数,计算时,会不会出问题。
后来,终于想到,在两个数计算时,其实是他们的补码序列在计算。这样就脱离了正负的约束,只是在读取时,会根据是否有符号来决定是否判断补码的标识位。
所以,不管a的正负,甚至是b的正负,在计算a(有符号)+b(有符号)与计算c(无符号)+b(无符号)结果的二进制补码序列相同。但是(int)会把无符号数强转为有符号的。这样在读取c+b结果表示的二进制补码序列时,是以有符号数的规则来读取的,这与读取a+b的结果相同。
2.【附加题】–实现一个Add函数,让两个数相加,但是不能使用+、-、*、/等四则运算符。ps:也不能用++、–等等
**
1,基础题:
**/* 注:本次代码处理的是不带环的单链表 */ #include <stdio.h> #include <stdlib.h> typedef struct ListNode { int _val; struct ListNode* _pNext; }Node, *PNode; /* 逆置 / 反转单链表: 思路:可以利用三个指针来标记三个连续节点。 1,将头结点的指针置为NULL 2,将头结点后面的_pNext在循环中改变指向: 第一个指针不变,逆置中间指针的指向,保留第三个指针的指向,作为往后遍历链表的路径。 这样遍历链表,每次逆置中间节点的指向,通过第三个节点往后遍历,将三个节点指针整体往后移一个节点。 最终改变整个链表。 通过以上思路,将链表分为三种情况: 1,链表为空。 2,链表只含有1个节点。 3,链表有2个及以上节点。 */ PNode ReverseList(PNode pHead) { PNode pPre = NULL; //第一个指针,指向连续节点中的第一个节点,最终成为新的头结点指针。 PNode pCur = NULL; //第二个指针,指向连续节点中的第二个节点,修改_pNext指向的指针。 PNode pTail = NULL; //第三个指针,指向连续节点中的第三个节点,保持与单链表连接的指针。 //链表为空的情况 if (NULL == pHead) return NULL; //链表只有一个节点的情况 if (NULL == pHead->_pNext) return pHead; //链表有2个及以上节点的情况 pPre = pHead; pCur = pPre->_pNext; pTail = pCur->_pNext; //处理pHead的_pNext指向 pHead->_pNext = NULL; //处理头结点之后的节点_pNext指向 while (pCur) { pCur->_pNext = pPre; pPre = pCur; pCur = pTail; if (pTail) //当该条件不成立时,表示pTail为NULL,下一次循环中pCur也NULL,将会跳出循环。 pTail = pTail->_pNext; } //此时pPre指向的是新的头结点 return pPre; } /* 查找单链表的倒数第k个节点,要求只能遍历一次链表: 思路:通过两个指针,一个前指针,一个后指针。 1,两个指针,初始都是指向单链表的头结点。 2,前指针先走K步。然后和后指针一起往后走。 3,直到前指针指向NULL。 注意,根据K的大小与单链表中元素个数的比较。可以分为: 1,K的值大于单链表中元素个数。 2,K的值小于等于单链表中元素个数。 3,需要注意K为0的情况 但是,链表中元素个数,必须通过遍历之后,才能知晓。 所以,可以在前指针往后走K步的过程中(即K-1步及之前),同时判断前指针是否为NULL。 如果前指针为NULL,表示单链表元素个数小于K。否则大于等于K。 */ PNode Find_the_penultimate_K_node(PNode pHead, size_t k) { //使用左、右来标识前、后指针。pHead是否为NULL,不影响程序逻辑。 PNode pLeft = pHead; PNode pRigth = pHead; if (!k) return NULL; //前指针先往后走K步 while (k && pLeft) { pLeft = pLeft->_pNext; --k; } if (k) //k>0,pLeft=NULL.表示元素个数小于K return NULL; //走到这一步,说明单链表元素个数大于等于K,然后,前后指针开始一起往后走。直到前指针为NULL while (pLeft) { pLeft = pLeft->_pNext; pRigth = pRigth->_pNext; } //返回倒数第K个节点指针。 return pRigth; } //打印单链表 void PrintList(PNode pHead) { while (pHead) { printf("%d->", pHead->_val); pHead = pHead->_pNext; } printf("NULL\n"); } int main() { int i = 0; Node node[10] = {0}; //建立10个node用来做实验 PNode pHead = &node[0]; //指向头结点 PNode pTemp = NULL; //指向倒数第K个节点 int k = 0; //给node赋值 for (; i < 10; ++i) node[i]._val = i; //构成一个单链表 for (i = 0; i < 9; ++i) node[i]._pNext = &node[i + 1]; node[9]._pNext = NULL; //注意,最后一个节点指向NULL PrintList(pHead); //打印旧的单链表 pTemp = Find_the_penultimate_K_node(pHead, 5); //寻找倒数第K个节点 printf("Find the penultimate K node is %d\n", pTemp->_val); pHead = ReverseList(pHead); //逆置单链表 PrintList(pHead); //打印新的单链表 system("pause"); return 0; }
**
2,附加题
**(1)利用两数相加,从后往前进位的思想
思路:
1,0和1可以表示某一位上的数值。(对于sum来说)
2,0和1还可以表示两种状态,即序列中某一位的状态(不需要进位与需要进位)。(对于carry来说)
3,a^b运算的结果是:二进制序列,不进位,只相加的结果sum。(0^0=0,1^0=1,1^1=0)
4,a&b运算,然后左移一位,的结果是:二进制序列中哪些位是需要进位carry。(0&0=0,1&0=0,1&1=1,然后将序列左移1位)
5,通过sum与carry带入3,4中的a与b,可以从后往前不断进位,直到carry为0,表示没有需要进位的位。
/* //递归版本 int Add_2(int a, int b) { if (0==b) return a; else { int sum = a ^ b; // 各位相加,不计进位 int carry = (a & b) << 1; // 记下进位 Add_2(sum, carry); // 求sum和carry的和 } } */ /**/ //迭代版本 int Add_2(int a, int b) { while (b) { int sum = a ^ b; int carry = (a & b) << 1; a = sum; b = carry; } return a; }
(2)利用数组来计算。
这种办法非常巧妙
思路:
1,数组通过下标的偏移,可以计算出基于首元素地址的某一元素位置,从而访问该元素。
那么我们可以利用编译器的这种行为,来让编译器自动帮我们进行加法运算。
2,32位下, int值与指针的sizeof大小都是4字节。char的大小为1字节。
所以,可以将两数a和b中。a强转为char*类型地址c。这样,b用来表示下标,计算c[b]时,是以1字节为单位计算偏移的。
*/
int Add(int a, int b) { char *c = (char *)a; //将a表示的值,看做是地址c。并且类型要是char*。因为sizeof(char)=1 return (int)&c[b]; //&a[b]-->a+sizeof(char)*b表示地址。通过(int)强转为int值 }
由于这种方法是从别处了解,本人第一次见,所以需要仔细分析分析
1,只是用于32平台?
是的,因为通过int值与指针值在32位平台下,都是4个字节,大小一样。所以在解析时,可以通过(char *) 、(int)来相互强转,而不会,多读取或者少读取字节数据。
2,b为负数时,可满足情况?
可以满足。因为,通过数组下标来访问元素的本质,就是基于某个地址的偏移。可以有正偏移(往后偏移),也可以有负偏移(往前偏移)。
测试用例:
int a[5] = {1,2,3,4,5}; int* p = NULL; p = &a[3]; printf("a[-1] = %d\n", p[-1]);//可以查看p[-1]的值是否为3
3,a为负数时,可满足情况吗?
可以满足。因为,在将a值转化为地址c时,会将a的补码序列,看做无符号数。我担心的是,在将有符号数化作无符号数,计算时,会不会出问题。
后来,终于想到,在两个数计算时,其实是他们的补码序列在计算。这样就脱离了正负的约束,只是在读取时,会根据是否有符号来决定是否判断补码的标识位。
所以,不管a的正负,甚至是b的正负,在计算a(有符号)+b(有符号)与计算c(无符号)+b(无符号)结果的二进制补码序列相同。但是(int)会把无符号数强转为有符号的。这样在读取c+b结果表示的二进制补码序列时,是以有符号数的规则来读取的,这与读取a+b的结果相同。
相关文章推荐
- 逆置、翻转链表/查找单链表的倒数第k个节点/A+B不使用四则运算++ -- 等
- 逆置,查找倒数第K个节点,Add函数不用四则运算的实现
- day02逆置/反转单链表+查找单链表的倒数第k个节点+实现一个Add函数不用四则运算
- C++实现单链表删除倒数第k个节点的方法
- C语言:【单链表】查找单链表的倒数第k个节点,要求只能遍历一次
- python实现单链表中删除倒数第K个节点的方法
- 剑指offer 15---查找单链表的倒数第k个节点,要求只能遍历一次链表
- 单链表的增删查 逆置 倒数第k个节点等问题
- C实现简单单向链表,一次遍历查找倒数第k个节点的值
- 单链表的创建(头插尾插),表长,输出,插入,删除,查找,逆置,分解长两个链表(奇数偶数链表),查找倒数第k个元素,产出相同元素
- 逆置/反转单链表+查找单链表的倒数第k个节点,要求只能遍历一次链表
- 查找单链表的倒数第k个节点,要求只能遍历一次链表
- 查找单链表的倒数第k个节点,要求只能遍历一次链表
- 查找单链表的倒数第k个节点
- 链表--查找单链表的倒数第k个节点,要求只能遍历一次链表
- 查找单链表的倒数第k个节点,要求只能遍历一次链表
- Java单链表基本操作(五)--查找倒数第K个节点
- 查找单链表倒数第K个节点和以及逆置单链表
- 反转单链表+合并有序单链表+查找单链表中倒数第k个节点--20150924
- 查找单链表的倒数第k个节点,要求只能遍历一次链表