剑指offer|解析和答案(C++/Python) (二)

剑指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.打印从1到最大的n位数
3.删除链表的节点
4.正则表达式匹配
5.表示数值的字符串
5.调整数组顺序使奇数位于偶数前面
6.链表中倒数第k个节点
7.链表中环的入口节点
8.反转链表
9.合并两个排序的链表
10.树的子结构

1.数值的整数次方

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

思路:
在做题之前要全面考虑各种情况,包括功能测试、边界测试及负面测试。
本题拟使用全局变量来说明是否有异常情况发生:0的负指数,会造成程序出错。并且0的0次方是没有数学意义,具体返回值需提前统一。
注意:double类型不能直接用 == 进行判断相等
1.常见方法。这种思路很简单,使用循环的方式进行不断相乘,但不高效。
2.高效方法。这种方法更高效,参考公式:
在这里插入图片描述
使用递归求解。
代码:
C++

class Solution {
public:
    //全局变量 表示是否获得错误输入
    //bool getInvialidInput = false;
    double Power(double base, int exponent) {
        //getInvialidInput = false;
        //0的负数次方会使程序出错  
        /*if(equal(base, 0.0) && exponent < 0){
            getInvialidInput = true;
            return 0.0;
        }*/
        //获得指数值的绝对值
        unsigned int absExponent = (unsigned int) exponent;
        if(exponent < 0){
            absExponent = (unsigned int) -exponent;
        }
        //0的0次方没有数学意义 需提前说明,这里统一为1,所以不需要额外代码
        double result = PowerWithExponent(base, absExponent);
        if(exponent < 0){
            result = 1.0/result;
        }
        return result;
    }
    double PowerWithExponent(double base, int exponent){
        //使用递归的方法高效计算
        double result ;
        //边界条件
        if(exponent == 0){
            result = 1;
            return result;
        }
        if(exponent == 1){
            result = base;
            return result;
        }
        //右移表示除2 效率比/高
        result = PowerWithExponent(base, exponent >> 1);
        //若exponent为偶数
        result = result * result;
        //若若exponent为奇数
        if(exponent & 0x1 == 1){
            result = result * base;
        }
        return result;
    }
};

Python

# -*- coding:utf-8 -*-
class Solution:
    def Power(self, base, exponent):
        # write code here
        # 获取指数的绝对值
        if exponent >= 0:
            absExponent = exponent
        else :
            absExponent = -exponent
        result = self.PowerWithExponent(base, absExponent)
        if exponent < 0:
            result = 1.0 / result
        
        return result
    def PowerWithExponent(self, base, absExponent):
        #边界条件
        if absExponent == 0:
            return 1
        if absExponent == 1:
            return base
        #递归求解
        result = self.PowerWithExponent(base, absExponent >> 1)
        #absExponent 是偶数
        result = result * result
        #absExponent 是奇数
        if absExponent & 0x1 == 1 :
            result = result * base
        return result
        

2.打印从1到最大的n位数

输入数字n,按顺序打印出从1到最大的n位十进制数。比如输入3,则打印出1,2,3一直到最大的3位数999。

思路:
1.n的大小没有限定,所以使用在字符串上模拟数字加法的解法
因为数字是n位的,所以我们需要应该长度为n+1的字符串,字符串的最后一位是’\0’。当实际数字不够n位的时候,字符串前半部分补0。
Incerement函数是通过while循环,使得数字不断加1,加1的数字是最小位,即ID是length - 1对应元素。并且当数字大于10时,会产生进位,即使得takeOver = 1。takeOver 会影响前一位的数值(i - 1),并通过计算unsigned char number = (unsigned char)(numbers[i] + takeOver - ‘0’); 再次判断(i - 1)位上的数字,若(i - 1)位上的数字大于10,再进位,以此类推。直到i = 0时,这个时候若number > 10,即出现999…99(n个9)进位的情况,即溢出,则终止递增。
PrintNumbers函数是打印数组,不过需要根据阅读习惯,从第一个非0的元素开始打印。
注意:一定要使用unsigned char来声明数组,我按照剑指offer用char来声明,会有错误,我估计是因为ascii码的范围是0~255,这个范围是unsigned char的范围,不是char的范围,所以会出错。
代码:(牛客网没有这题,无法验证Python代码)
C++

class Solution {
public:
    void Print1TomaxOfNDigits(int n){
        if(n <= 0)
            return;
        unsigned char *numbers = new unsigned char[n + 1];
        int length = n + 1;
        //初始化数组  最后一位是结束符 '0'
        for(int i = 0; i < n; ++i){
            numbers[i] = '0';
        }
        numbers[n] = '\0';
        while(!Incerement(numbers, length - 1)){//判断是否溢出
            PrintNumbers(numbers, length - 1);//打印数组
        }
        delete[] numbers;
    }
    bool Incerement(unsigned char* numbers, int numLength){
        bool overFlow = false;//溢出标志
        int takeOver = 0;//是否进位
        //int number = 0;//个位的数字
        for(int i = numLength - 1; i >= 0; --i){
            unsigned char number = (unsigned char)(numbers[i] +  takeOver - '0');
            if(i == numLength - 1){//最后一位进行递增
                ++number;
            }
            if(number >= 10){
                if(i == 0){
                    overFlow = true;
                }else{
                    number = number - 10;//保留个位
                    takeOver = 1;//进位
                    numbers[i] = '0' + number;
                }
            }else{//没有出现进位的情况
                numbers[i] = '0' + number;

                break;
            }
        }

        return overFlow;
    }
    void PrintNumbers(unsigned char* numbers, int numLength){
        bool beginIs0 = true;
        for(int i = 0; i < numLength  ; ++i){

            if(beginIs0 && numbers[i] != '0'){
                beginIs0 = false;
            }
            if(!beginIs0){
                cout<<numbers[i];
            }
        }
        cout<<"\t";

    }
};
	

2.递归。上面的方法很直观,但代码量较大,使用递归的方法会让代码更简洁。如果在数字前面补0,就会发现n位所有十进制数其实就是n个从0到9的全排列。全排列用递归容易表达,数字的每一位都可能是0~9中的一个数,然后设置下一位。递归结束条件:已经设置了数字的最后一位。
C++:

class Solution {
public:
    void Print1TomaxOfNDigits(int n){
        if(n <= 0)
            return;
        unsigned char *numbers = new unsigned char[n + 1];
        int length = n + 1;
        //初始化数组  最后一位是结束符 '0'
        for(int i = 0; i < n; ++i){
            numbers[i] = '0';
        }
        numbers[n] = '\0';
        //全排列
        for(int i = 0; i <= 9; ++i){
            numbers[0] = i + '0';
            Incerement(numbers, length - 1, 0);
        }

        delete[] numbers;
    }
    void Incerement(unsigned char* numbers, int numLength, int index){
        //边界条件
        if(index == numLength - 1){
            PrintNumbers(numbers, numLength);
            return;
        }
        //从0~9全排列
        for(int i = 0; i <= 9; ++i){
            numbers[index + 1] = i + '0';
            Incerement(numbers, numLength , index + 1);
        }

    }
    void PrintNumbers(unsigned char* numbers, int numLength){
        bool beginIs0 = true;
        for(int i = 0; i < numLength  ; ++i){

            if(beginIs0 && numbers[i] != '0'){
                beginIs0 = false;
            }
            if(!beginIs0){
                cout<<numbers[i];
            }
        }
        cout<<"\t";

    }
};

3.删除链表的节点

题目1:在O(1)时间内删除链表节点
给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点。链表节点与函数的定义如下:
struct ListNode {
int val;
struct ListNode next;
ListNode(int x) :
val(x), next(nullptr) {
}
};
void DeleNode(ListNode
pListHead, ListNode* pTOBeDeleted);

思路:
参考图示:
在这里插入图片描述
图中(1)所示是一个链表。按照从头节点顺序查找,并删除节点i,时间复杂度是O(n),如图中(2)所示。但如果吧下一个节点的内容复制到需要删除的节点上覆盖原有的内容,再把下一个节点删除,就相当于把当前需要删除的节点删除了。
如要删除节点i,先把i节点的下一个节点j的内容复制到i,然后把i的指针指向节点j的下一个节点。此时再删除节点j,其等价于把节点i删除了,如图中(3)所示。
特殊情况
1.链表中只有一个节点,需要删除链表的头节点(也是尾节点),那么在删除之后,需要把头节点设置为nullptr。
2.删除的节点在链表尾部,那么从链表头节点开始顺序遍历到需要删除的节点,完成删除工作。
注意:
1.对于n - 1个非尾部节点的删除的时间复杂度是O(1),对于1个尾节点的删除的时间复杂度是O(n),那么总的平均复杂度是[(n - 1)xO(1) + O(n)]/n ,结果仍然是O(1)。
2.这段代码有一个假设(前提):要删除的节点的确在链表中。否则需要O(n)的时间复杂度去判断链表中是否包含某一节点。
代码(牛客网没有这题,Python代码无法验证)
C++:

class Solution {
public:
	void DeleNode(ListNode* pListHead, ListNode* pTOBeDeleted){
		//判断是否为空指针
		if(!pListHead || !pTOBeDeleted)
			return;
		//要删除的结点不是尾节点
		if(pTOBeDeleted -> next != nullptr){
			ListNode* pNext = pTOBeDeleted -> next;
			pTOBeDeleted -> val = pNext -> val;
			pTOBeDeleted -> next = pNext -> next;
			delete pNext;
			pNext = nullptr
		}else if(pListHead == pTOBeDeleted){//只有一个节点 删除的既是头节点也是尾结点
			delete pTOBeDeleted;
			pTOBeDeleted = nullptr;
			pListHead = nullptr;
		}else{//要删除的是尾节点 必须从头节点开始遍历
			ListNode* pNext = pListHead;
			while(pNext -> next != pTOBeDeleted){
				pNext = pNext -> next;
			}
			pNext -> next = nullptr;
			delete pTOBeDeleted;
			pTOBeDeleted = nullptr;
		}
	}
		
};

题目2:删除链表中重复的节点。
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

思路:
解决这个问题首先需要确定要删除的节点是什么。由于给了头节点,所以可以采取遍历的方法进行查找。比如使用连续两个指针,如果两个指针对应的节点的值相等,那么让第二个指针继续遍历,直到不一样,然后删除两个指针之间中间的节点(包括第一个指针),依此类推。但这样少考虑了特殊情况,因为头指针就可能是重复的节点。所以,两个指针无法解决这个问题,因此需要使用3个指针,同时在头节点之前再新建一个节点。
可以考虑以下几种特殊情况:

  • 1——>1——>1——>1——>1——>1。
  • 1。
  • nullptr(None)。

当新建一个节点时,就会变成:

  • newHead——>1——>1——>1——>1——>1——>1。
  • newHead——>1。
  • newHead——>nullptr(None)。

那么实际返回指针便是newHead——>next,而不是pHead。因为pHead有可能被删除了
总结:使用3个指针。使用pNode和pNext(pNode——>next)遍历链表查找重复的节点,使用pPre从newHead开始链接删除重复节点后的链表。
代码:
c++

class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
		if(pHead == nullptr)
			return pHead;
		if(pHead -> next == nullptr)
			return pHead;
        ListNode* pNewHead = new ListNode(-1);
        pNewHead -> next = pHead;
        ListNode* pNode = pHead;
        ListNode* pPre = pNewHead;		
		ListNode* pNext = pNode -> next;
		//需要考虑从头节点开始重复的情况
		while( pNode -> next != nullptr&& pNode != nullptr)
            {
            if(pNode -> val == pNext -> val)
            {
                while( pNode -> val == pNext -> val && pNext != nullptr)
                {
                    ListNode* temp = pNext;
                    pNext = pNext -> next;
                    delete temp;
                    temp = nullptr;
                }
                delete pNode;
                pPre -> next = pNext;
                pNode = pNext;
                pNext = pNode -> next;
            }
            else
            {
                pPre = pNode;
                pNode = pNode -> next;
                pNext = pNode -> next;
            }
		}
		return pNewHead->next;//千万千万不要使用pHead  因为原来的pHead可能已经被删除了
    }
};

Python
在使用Python的时候注意:
在if或者while判断中,要先判断pNode或者pNext不等于None,才能判断pNode.val == pNext.val。如果调换顺序,由于pNext本身可能是None,则没有val属性,会报错。next同理,所以会把pNext = pNode.next房子while循环的开头。

class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        if pHead == None or pHead.next == None:
            return pHead
        pNewHead = ListNode(-1)
        pNewHead.next = pHead
        pNode = pHead
        pPre = pNewHead
        pNext = pNode.next
        #从头开始遍历链表
        while pNode != None and pNode.next != None:
            pNext = pNode.next
            if pNode.val == pNext.val:
                while pNext != None and pNode.val == pNext.val:                    
                    pNext = pNext.next
                pPre.next = pNext
                pNode = pNext 
                
            else :
                pPre = pNode
                pNode = pNode.next            
                
        return pNewHead.next

4.正则表达式匹配

请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配

思路:
思路:
由于字符匹配具有多种情况,所以本题采取递归的方式进行判罚。
主要分为两个大类:
A.模式的第二个字符不是’*’
1.若字符串的第一个字符和模式中的第一个字符相同,移位匹配下一个字符。
2.若模式中的第一个字符是’.’,则可以匹配字符串中的任意字符(’\0’是空字符,不是字符)。
3.若字符串中的第一个字符和模式中的第一个字符不相等,返回False。
B.模式的第二个字符是’*’,这种情况较复杂
1.若字符串的第一个字符和模式中的第一个字符相同或者若模式中的第一个字符是’.’,则可以匹配字符串中的任意字符,则有3种情况:
(1)字符串和模式均移位:str + 1, pattern + 2。
(2)忽略模式中’*‘前的字符(’*‘前面的字符可以出现任意次,含0次):str, pattern + 2。
(3)让模式中的’*‘前的字符重复一次(’‘前面的字符可以出现任意次):str + 1, pattern。
2.若字符串的第一个字符和模式中的第一个字符不同,且模式中的第一个字符不是’.’:str, pattern + 1。
递归的边界条件:
1.当str和pattern都同时遍历完,则返回True。
2.当str没有遍历完,pattern遍历完,则返回False。
(3.当str遍历完,而pattern没有遍历完,不是边界条件,考虑特殊情况’’,’.*’,以及’’,’’。这两个例子均是True。)
代码:
C++

class Solution {
public:
    bool match(char* str, char* pattern)
    {
		if(str == nullptr|| pattern == nullptr){
			return false;
		}
		return matchCore(str, pattern);
    }
	bool matchCore(char* str, char* pattern){
		//边界条件
		if(*str == '\0' && *pattern == '\0'){
			return true;
		}
		if(*str != '\0' && *pattern == '\0'){
			return false;
		}
        if(*str == '\0' && *pattern != '\0'){
			return false;
		}
		if(*(pattern + 1) == '*'){
                //当前字符相等 且'*'后的字符相等 '.'可以标识任意字符 '\0'是空字符 不是字符
			if(*pattern == *str || (*pattern == '.' && *str != '\0')){
				//字符串和模式同时后移
				return matchCore(str + 1, pattern + 2)
				//模式'*'前的字符重复
				|| matchCore(str + 1, pattern)
				//忽略 '*'前的模式
				|| matchCore(str, pattern + 2);
			}else{
				//忽略 '*'
				return matchCore(str, pattern + 2);
			}
		}
		if(*str == *pattern || (*pattern == '.' && *str != '\0')){
			return matchCore(str + 1, pattern + 1);
		}
		return false;			
	}	
};

Python:
python需要注意:Python不支持单字符类型,单字符在 Python 中也是作为一个字符串使用。字符串中没有 ‘\0’。为了避免 string index out of range 需有使用len()对其判断。

# -*- coding:utf-8 -*-
class Solution:
    # s, pattern都是字符串
    def match(self, s, pattern):
        # write code here
        if s == None or pattern == None:
            return False
        return self.matchCore(s, pattern)
    def matchCore(self, s, pattern):
        #边界条件
        #Python不支持单字符类型,单字符在 Python 中也是作为一个字符串使用。
        #字符串中没有 '\0'        
        if len(s) == 0 and len(pattern) == 0:
            return True
        if len(s) > 0 and len(pattern) == 0:
            return False
        #特例: '','.*'   
        #先判断模式的下一个字符是否等于'*'
        #为了避免 string index out of range 需有使用len()对其判断
        if len(pattern) > 1 and pattern[1] == '*':
            #当前字符相等 且'*'后的字符相等 '.'可以标识任意字符 '\0'是空字符 不是字符
            #python 中没有'\0' 只能通过 len()得到字符串长度进而判断是否为空字符串
            if len(s) > 0 and ( s[0] == pattern[0] or pattern[0] == '.' ):
                        #1.字符串和模式同时后移
                        #2.忽略模式中'*'前的字符
                        #3. 让模式中的'*'前的字符重复一次
                return self.matchCore(s[1:], pattern[2:]) \
                        or self.matchCore(s, pattern[2:]) \
                        or self.matchCore(s[1:], pattern)
            else :#当前字符不相等
                return self.matchCore(s, pattern[2:])
        if len(s) > 0 and ( s[0] == pattern[0] or pattern[0] == '.' ):
            return self.matchCore(s[1:], pattern[1:])
        #如果第一个字符不相等 则直接不能匹配
        return False

5.表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

思路:
这个题注意注意一些条件,以遍历整个字符串的方式进行判断。
写的时候可以参考通式: +/- A . B e/E +/- C
1.A 、 B 、C的判断方式是一样的,即判断全是数字组成即可。
2.+/- 只能出现在A 和C的前面 且只能出现一次。
3. 小数点. 只能出现一次,可以是:
(a)A.,小数点后面可以没有整数部分。
(b).B, 小数点前面可以没有整数部分。
(c)A.B, 小数点前后都可以有数字。
4.指数E/e:
(a)e/E前面必须有数字,.e1,e2不能表示数字。
(b)e/E后面必须有数字,且不能有小数点,1e,e+1.1不能表示数字。
因此使用函数scanUnsignedInteger扫描数字(A、B、C),当遇到小数点’.'或者’e’以及’E’时,跳出,进行逻辑判断。
代码:
C++

class Solution {
public:
    bool isNumeric(char* string)
    {
        bool flagDouHao = true;
        bool flagENum = false;//e的前面必须有数字
        if(string == nullptr)
            return false;
        if(*string  == '+' || *string == '-'){
            ++string;
        }
        if(*string == '.'){//1.小数点可以没有整数部分
            ++string;
            flagDouHao = false;//小数点已经出现一次且只能出现一次
        }
        //第一个整数部分
        char* before = string;
        char* after = scanUnsignedInteger(string);
        string = after;
        bool numeric = before < after;//若地址改变 则有数字存在
        if(before < after)
            flagENum = true;//有数字
        if(*string == '.'){//2.小数点后面可以没有数字
            ++string;
            numeric = numeric && flagDouHao;//若小数点出现第二次直接不行
        }else if(*string == 'e' || *string == 'E'){//第一次截断 也可能是出现e
            ++string;
            if(*string  == '+' || *string == '-')//先去掉符号
                ++string;
            char* before = string;
            char* after = scanUnsignedInteger(string);
            string = after;
            numeric = numeric && (before < after) && flagENum;
            return numeric && (*string == '\0');
        }
        if(*string =='\0'){
            numeric = numeric && true;
        }else{//3.当然小数点后面可以有数字
            char* before = string;
            char* after = scanUnsignedInteger(string);
            string = after;
            if(before < after)
                flagENum = true;//有数字
            numeric = numeric && (before < after);//若地址改变 则有数字存在
        }
        //小数点只能出现一次 且不能在指数部分因此 下面对指数E/e进行判断
        if(*string =='e' || *string =='E'){
            ++string;
            if(*string  == '+' || *string == '-')//先去掉符号
                ++string;
            char* before = string;
            char* after = scanUnsignedInteger(string);
            string = after;
            numeric = numeric && (before < after) && flagENum;//若地址改变 则有数字存在 并且E/e前面有数字在
        }
        //字符串只能以'\0'结尾
        return numeric && (*string == '\0');
    }
    char* scanUnsignedInteger(char* str){
        while(*str != '\0' && *str >= '0' && *str <= '9'){
            ++str;
        }
        return str;
    }
};

Python:
python只能用len()来进行判断字符串是否结束。

# -*- coding:utf-8 -*-
class Solution:
    # s字符串
    def isNumeric(self, s):
        # write code here
        flagDouHao = True#逗号只能出现一次
        flagENum = False#E/e前面必须有数字
        if len(s) == 0:
            return False
        if len(s) > 0 and (s[0] == '+' or s[0] == '-'):#先去掉+ - 号
            s = s[1:]
        if len(s) > 0 and s[0] == '.':#1.小数点可以没有整数部分
            s = s[1:]
            flagDouHao = False #逗号已经出现了一次
        before = len(s)
        s = self.scanUsignedNum(s) 
        after = len(s)
        numeric = (before > after)
        if before > after:
            flagENum = True
        #2.小数点后面可以没有数字
        if len(s) > 0 and s[0] == '.':
            s = s[1:]#后移一位进行判断
        elif len(s) > 0 and (s[0] == 'e' or s[0] == 'E' ):#第一截断 可能是遇到了e/E
            s = s[1:]
            if len(s) > 0 and (s[0] == '+' or s[0] == '-'):#先去掉+ - 号
                s = s[1:] 
            before = len(s)
            s = self.scanUsignedNum(s)
            after = len(s)
            numeric = (before > after) and flagENum 
            return numeric and len(s) == 0            
            
        if len(s) == 0:
            numeric = numeric and True
        else :#3.小数点当然也可以有数字
            before = len(s)
            s = self.scanUsignedNum(s) 
            after = len(s)
            numeric = (before > after) and flagDouHao
            if before > after:
                flagENum = True
        
        #若有小数点 则E/e必然在小数点后面
        if len(s) > 0 and (s[0] == 'e' or s[0] == 'E' ):
            s = s[1:]
            if len(s) > 0 and (s[0] == '+' or s[0] == '-'):#先去掉+ - 号
                s = s[1:] 
            before = len(s)
            s = self.scanUsignedNum(s)
            after = len(s)
            numeric = (before > after) and flagENum        
        return numeric and len(s) == 0
        
    def scanUsignedNum(self, s):
        for i in range(len(s)):
            if s[i] <='9' and s[i] >= '0':
                continue
            else :
                return s[i:]
        #纯数字则返回空列表
        return s[len(s):]

5.调整数组顺序使奇数位于偶数前面

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

思路:
思路:
如果本题没有要求“奇数和奇数,偶数和偶数之间的相对位置不变”,那么会有优化思路:
设置两个指针,一个在头,一个在尾,无非以下四种情况。
1.头:奇数(右移) 尾:偶数(左移)。
2.头:奇数(右移) 尾:奇数(不变)。
3.头:偶数(交换) 尾:奇数(交换)。
4.头:偶数(不变) 尾:偶数(左移)。
知道头指针地址>尾指针地址。
不过在牛客网上有限制条件:“奇数和奇数,偶数和偶数之间的相对位置不变”,因此上述优化方法无效,现提出两种方法。
1.新建一个向量(列表),使用额外内存开销,直接存储奇数和偶数。
代码:
C++

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        int length = array.size();
        vector<int> arr;//新建一个向量储存
        for(int i = 0; i < length; ++i){
            if((array[i] & 0x01) == 1)
                arr.push_back(array[i]);//先放奇数
        }
        for(int i = 0; i < length; ++i){
            if((array[i] & 0x01) != 1)
                arr.push_back(array[i]);//再发偶数
        }
        //交换赋值
        array.swap(arr);
    }
};

Python

class Solution:
    def reOrderArray(self, array):
        # write code here
        arr =  []#新建一个列表
        for i in range(len(array)):
            if array[i] & 0x1 == 1:#先放奇数
                arr.append(array[i])
        for i in range(len(array)):
            if array[i] & 0x1 == 0:#再放偶数
                arr.append(array[i])        
        return arr

# -*- coding:utf-8 -*-
class Solution:
    def reOrderArray(self, array):
        # write code here
        odd,even=[],[]
        for i in array:
            odd.append(i) if i%2==1 else even.append(i)
        return odd+even

2.使用类似冒泡排序的算法,时间复杂度O(n^2),如果遇到偶奇排序,直接交换两个数字。
C++

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        int length = array.size();
        for(int i = 0; i < length - 1; ++i){
            for(int j = 0; j < length - 1 - i; ++j){
                if((array[j]&0x01) == 0 && (array[j + 1]&0x01) != 0){//偶奇排列则相互交换顺序
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
            }
        }
    }
};

Python

class Solution:
    def reOrderArray(self, array):
        for i in range(len(array)):
            for j in range(len(array) - 1 - i):
                if (array[j] & 0x1) == 0 and (array[j + 1] & 0x1) == 1:#偶奇交换顺序
                    temp = array[j]
                    array[j] = array[j + 1]
                    array[j + 1] = temp
        return array

6.链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个结点。
备注:为符合大多数人习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如一个链表有6个节点,从头节点开始,它们的值依次是1,2,3,4,5,6。这个链表的倒数第3个节点是值为4的节点。

思路:
由于题目链表是单向链表,所以不能采取先走到链表的尾端,再从尾端回溯到k步。
在这里插入图片描述
设置两个指针pFirst和pSecond。如图所示:
(a)pFirst先走k - 1步。
(b)pSecond指向头指针。
(c)pFirst和pSecond在第k步时同时移动,直到pFirst指向尾节点。
这种方法简单高效,只需要遍历一次即可。
但为了保证代码的鲁棒性需要注意:
1.输入的头指针pListHead不能为空指针,否则代码会试图访问空指针指向的内存,造成程序崩溃。
2.输入的已pListHead为头节点的链表总数不能少于k,否则会由于在for循环中会在链表上向前走k - 1步,仍然会由于空指针造成程序崩溃。
3.输入的参数k不能为0,由于k是一个无符号整数,那么在for循环中k - 1得到的将不是 -1,而是4294967295(无符号的0xFFFFFFFF)。因此for循环执行的次数会远远超出预计,同样造成奔溃。
代码:
C++:

/*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(k == 0 || pListHead == nullptr){//k=0和空指针没有意义
			return nullptr;
		}
		ListNode* pFirst = pListHead;
		ListNode* pSecond = pListHead;
		//第一个指针先走k - 1步
		for(unsigned int i = 0; i < k - 1; ++i){
			//保证链表长度大于k
			if(pFirst -> next != nullptr){
				pFirst = pFirst -> next;
			}else{
				return nullptr;
			}
		}
		//当第一个指针已经走k-1步  第一个和第二个指针从第k步一起走 直到第一个指针走到尾指针
		while(pFirst -> next != nullptr){
			pFirst = pFirst -> next;
			pSecond = pSecond -> next;
		}
		return pSecond;
    }
};

Python:

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def FindKthToTail(self, head, k):
        # write code here
        if k == 0 or head == None:
            return None
        pFirst = head
        pSecond = head
        #先把第一个指针移动k - 1步
        for i in range(k - 1):
            if pFirst.next != None:
                pFirst = pFirst.next
            else :#保证链表长度大于k
                return None
        #当第一个指针已经走k-1步  第一个和第二个指针从第k步一起走 直到第一个指针走到尾指针
        while pFirst.next != None:
            pFirst = pFirst.next
            pSecond = pSecond.next
        
        return pSecond
        
        

7.链表中环的入口节点

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

思路:
这个问题分3步走:
1.首先判断链表中是否有环存在。使用两个指针,一个快,一个慢,同时从头节点出发,若在链表中相遇,则存在环。若快指针先遍历完(pFast->next = nullptr),则链表中不存在环。
2.若存在环,则判断环中有多少个节点。返回第一步相遇的指针pFast,从pFast开始计数,直到再次返回到pFast的初始位置,则计数值为环节点的数目为numNodes。
3.判断环的入口节点。根据第2步得到的环节点数目,生成两个指针pFirst和pSecond,pFirst先移动numNodes步,接下来pFirst和pSecond同时移动,直到相遇。相遇的节点即为环的入口节点。
参考图示:
在这里插入图片描述
代码:
C++:

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
		ListNode* meetNode = MeetingNode(pHead);//先判断是否有环
		if(meetNode == nullptr)
			return nullptr;
		//有环则判断有几个节点在环中
		int numNode = 1;
		ListNode* pNode = meetNode;
		while(pNode -> next != meetNode){
			pNode = pNode -> next;
			++numNode;
		}
		//两个指针同时指向头指针 先让一个指针移动numNode步
		ListNode* pFirst = pHead;
		ListNode* pSecond = pHead;
		for(int i = 0; i < numNode; ++i){
			pFirst = pFirst -> next;
		}
		//移动之后再同时移动 相遇的节点便是入口节点
		while(pFirst != pSecond){
			pFirst = pFirst -> next;
			pSecond = pSecond -> next;
		}
		return pFirst;
    }
	ListNode* MeetingNode(ListNode* pHead){//寻找快慢指针相遇的节点 判断是否有环
		ListNode* pSlow = pHead;
		ListNode* pFast = pHead;
		if(pHead == nullptr){
			return nullptr;
		}
		if(pSlow -> next != nullptr){
			pFast = pFast -> next;
		}
		while(pFast -> next != nullptr){
			if(pSlow == pFast)
				return pFast;//相等则返回相遇的节点
			pSlow = pSlow -> next;
			pFast = pFast -> next;
			if(pFast -> next != nullptr)
				pFast = pFast -> next;
		}
		return nullptr;
	}
};

Python:

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def EntryNodeOfLoop(self, pHead):
        # write code here
        meetNode = self.MeetingNode(pHead)
        if meetNode == None:
            return None
        #已经找到环 开始判断环有多少个节点、
        numNodes = 1
        pNode = meetNode
        while pNode.next != meetNode:
            pNode = pNode.next
            numNodes = numNodes + 1
        #计算完环节点数目后找入口
        pFirst = pHead
        pSecond = pHead
        for i in range(numNodes):
            pFirst = pFirst.next
        while pFirst != pSecond:
            pFirst = pFirst.next
            pSecond = pSecond.next
        return pFirst
    def MeetingNode(self, pHead):
        if pHead == None:
            return None
        pSlow = pHead
        pFast = pHead
        if pSlow.next == None:
            return None
        pFast = pSlow.next   
        while pFast.next != None:
            if pSlow == pFast:#相遇则返回
                return pFast
            pSlow = pSlow.next
            pFast = pFast.next
            if pFast.next != None:
                pFast = pFast.next
        return None

8.反转链表

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

思路:
先对链表进行判断,如果链表是空或者链表只有一个节点,则直接返回该链表。如果链表节点大于等于2个,则使用三个指针来完成。
容易出现的问题:
1.输入的链表头为nullptr或者整个链表只有一个节点,程序立即崩溃。
2.反转后的链表出现断裂。
3.返回的反转之后的头节点不是原始链表的尾节点。
测试用例:
1.输入的链表头指针是nullptr。
2.输入的链表只有一个节点。
3.输入的链表有多个节点。
参考图示:
在这里插入图片描述
代码:
C++:

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        //如果没有或者只有一个节点时
        if(pHead == nullptr || pHead -> next == nullptr){
            return pHead;
        }
        ListNode* pReverseHead = nullptr;
        ListNode* pNode = pHead;
        ListNode* pPre = nullptr;
        while(pNode -> next != nullptr){
            pReverseHead = pNode -> next;
            pNode -> next = pPre;
            pPre = pNode;
            pNode = pReverseHead;
        }
        pReverseHead -> next = pPre;
        return pReverseHead;
    }
};

Python

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    # 返回ListNode
    def ReverseList(self, pHead):
        # write code here
        if pHead == None or pHead.next == None:
            return pHead
        pNode = pHead
        pReverHead = None
        pPre = None
        while pNode.next != None:
            pReverHead = pNode.next
            pNode.next = pPre
            pPre = pNode
            pNode = pReverHead
        pReverHead.next = pPre
        return pReverHead

9.合并两个排序的链表

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

思路:
参考图示:
在这里插入图片描述
1.先找到两个链表中头节点,把值较小的的节点取出。
2.再继续比较剩余两个链表的头节点,把值较小的的节点取出。
3.重复上述步骤,直到某一个链表为空(NULL/nullptr)
代码:
C++:

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1 == NULL && pHead2 != NULL){
            return pHead2;
        }else if(pHead1 != NULL && pHead2 == NULL){
            return pHead1;
        }else if(pHead1 == NULL && pHead2 == NULL){
            return NULL;
        }
        ListNode* pMergeHead = NULL;
        if(pHead1 -> val <= pHead2 -> val){
            pMergeHead = pHead1;
            pMergeHead -> next = Merge(pHead1 -> next, pHead2);
        }else{
            pMergeHead = pHead2;
            pMergeHead -> next = Merge(pHead1, pHead2 -> next);
        }
        return pMergeHead;
    }
};

Python

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    # 返回合并后列表
    def Merge(self, pHead1, pHead2):
        # write code here
        if pHead1 == None and pHead2 != None:
            return pHead2
        if pHead1 != None and pHead2 == None:
            return pHead1
        if pHead1 == None and pHead2 == None:
            return None
        pMergeHead = None
        if pHead1.val <= pHead2.val:
            pMergeHead = pHead1
            pMergeHead.next = self.Merge(pHead1.next, pHead2)
        else:
            pMergeHead = pHead2
            pMergeHead.next = self.Merge(pHead1, pHead2.next)
        return pMergeHead

10.树的子结构

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

思路:
使用递归操作,分为两步:
1.在树A中找到和树B根节点的值一样的节点R。
2.判断A树中以R为根节点的子树是不是包含和树B一样的结构。
3.若是返回Ture,若不是继续遍历树A左右节点,寻找和树B根节点的值一样的节点R。
参考图示:
在这里插入图片描述
注意:
1.需要检查边界条件,即检查空指针,否则程序容易崩溃。
2.如果树中的节点值是double/float型,则不能使用pRoot1 -> val == pRoot2 -> val,这是因为计算机内表示小数时都存在误差。判断两个小数是否相等,只能判断它们之间的绝对值是不是在一个很小的范围内。参考代码:

bool Equal(double num1, double num2){
    if((num1 - num2 > -0.0000001) && (num1 - num2 < 0.0000001))
        return true;
    else
        return false;
}

代码:
C++

class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        bool isTree = false;
        //先在pRoot1 找到 pRoot2跟节点值一样的节点
        if(pRoot1 == NULL || pRoot2 == NULL){
            return false;
        }
        if(pRoot1 -> val == pRoot2 -> val){//若根节点相等进行判断
            //进行判断是否是子树
            isTree = doesTreeHasTree2(pRoot1, pRoot2);
        }
        //不相等继续判断左右子树
        isTree = HasSubtree(pRoot1 -> left, pRoot2)
                || HasSubtree(pRoot1 -> right, pRoot2) || isTree;

        return isTree;
    }
    bool doesTreeHasTree2(TreeNode* pRoot1, TreeNode* pRoot2){

        if(pRoot2 == NULL){//第二个树遍历完了
            return true;
        }
        if(pRoot1 == NULL){//第一个树遍历完了
            return false;
        }
        if(pRoot1 -> val == pRoot2 -> val){
            return doesTreeHasTree2(pRoot1 -> left, pRoot2 -> left)
                && doesTreeHasTree2(pRoot1 -> right, pRoot2 -> right);
        }
        //不相等的时候返回false
        return false;
    }
};

Python

# -*- coding:utf-8 -*-
#class TreeNode:
#    def __init__(self, x):
#        self.val = x
#        self.left = None
#        self.right = None
class Solution:
    def HasSubtree(self, pRoot1, pRoot2):
        # write code here
        isTree = False
        if pRoot1 == None or pRoot2 == None:
            return False
        #先找pRoot1中的节点的值等于pRoot2跟节点的值
        if pRoot1.val == pRoot2.val:
            #若节点相等 进行判断是否存在子树
            isTree = self.doesTreeHasTree2(pRoot1, pRoot2)
        #继续遍历左右子树
        isTree = self.HasSubtree(pRoot1.left, pRoot2) \
                or self.HasSubtree(pRoot1.right, pRoot2) \
                or isTree
        return isTree
    def doesTreeHasTree2(self, pRoot1, pRoot2):
        if pRoot2 == None:
            return True
        if pRoot1 == None:
            return False
        if pRoot1.val == pRoot2.val:
            #遍历左右节点
            return self.doesTreeHasTree2(pRoot1.left, pRoot2.left) \
                and self.doesTreeHasTree2(pRoot1.right, pRoot2.right)
        return False
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值