• 【数据结构与算法】《剑指offer》学习笔记----第三章 高质量的代码(含16-26题)


    第3章 高质量的代码

    从3个方面保证代码的完整性:

    测试内容 要求
    功能测试 完成常规的功能要求
    边界测试 考虑各种边界值,如循环终止条件,递归终止条件
    负面测试 考虑各种可能的错误输入,不合法输入的处理

    有3中错误处理方法:

    方法 优点 缺点
    返回值 和系统API一致 不能方便地使用计算结果
    全局变量 能方便地使用计算结果 用户可能会忘记检查全局变量
    异常 可以为不同错误原因定义不同的异常类型,逻辑清晰 有些语言不支持异常,抛出异常时对性能有负面影响

    面试题16. 数值的整数次方

    实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

    示例 1:
    输入: 2.00000, 10
    输出: 1024.00000
    
    
    示例 2:
    输入: 2.10000, 3
    输出: 9.26100
    
    
    示例 3:
    输入: 2.00000, -2
    输出: 0.25000
    解释: 2-2 = 1/22 = 1/4 = 0.25
     
    
    说明:
    -100.0 < x < 100.0
    n 是 32 位有符号整数,其数值范围是 [231, 2311]

    这道题是让实现指定的库函数pow。

    class Solution {
    public:
        double myPow(double x, int n) {
            if(x == 1 || n == 0) return 1;//1的任意次方,和任何数的0次方,都让返回1
            double res = 1;
            long num = n;
            if(n < 0){//指数为负值时,将指数变为正值,且底数变为底数的倒数
                num = -num;
                x = 1/x;
            }
            while(num){//如果指数没有消耗完,两次方两次方的降
                if(num & 1) res *= x;//指数的低位存在,res = res * 底数
                x *= x;//x = x*x,x扩大为它的平方,因为二进制每位的差距是平方关系
                num >>= 1;//指数除以2
            }
            return res;
        }
    };
    

    面试题17. 打印从1到最大的n位数

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

    示例 1:
    输入: n = 1
    输出: [1,2,3,4,5,6,7,8,9]
     
    说明:
    用返回一个整数列表来代替打印
    n 为正整数
    

    递归解法:

    class Solution
    {
    public:
    	vector<int> res;
    	vector<int> printNumbers(int n) {
    		if (n <= 0) return res;
    		string number(n, '0');
            //从高位到低位进行全排列
    		for (int i = 0; i <= 9; ++i)
    		{
    			number[0] = i + '0';//首字符赋初值
    			permutationNumbers(number, n, 1);//设置下一位
    		}
    		return res;
    	}
    	//对数字全排列
    	void permutationNumbers(string& number, int length, int index) {
    		if (index == length) {//递归边界
    			saveNumber(number);//存储结果
    			return;
    		}
    		else
    		{
    			for (int i = 0; i <= 9; i++)
    			{
    				number[index] = '0' + i;//设置第index位的字符
    				permutationNumbers(number, length, index + 1);
    			}
    		}
    	}
    	//存储结果
    	//只能存储前导非0的排列
    	void saveNumber(string number) {
    		bool isBegin0 = true;
    		string tempStr = "";
    		string::iterator it = number.begin();
    		while (it != number.end())//遍历字符串
    		{
    			if (isBegin0 && *it != '0') isBegin0 = false;//如果标志位为true,且当前确实不是'0',将标志位更新为false
    			if (!isBegin0) {
    				tempStr += *it;
    			}
    			it++;
    		}
    		//从高位到低位全排列,要注意首字符为0时,tempStr为空,不能执行stoi
    		if (tempStr != "") {
    			int tempNum = stoi(tempStr);
    			res.push_back(tempNum);
    		}
    	}
    };
    

    大数string表示:

    class Solution {
    public:
    	vector<int> res;
    	vector<int> printNumbers(int n) {
    		if (n <= 0) return res;
    		//创建一个能容纳最大值的字符数组
    		string number(n, '0');
    		//初始全部设置为0
    		while (!Increment(number))
    		{
    			saveNumber(number);
    		}
    		return res;
    	}
    	bool Increment(string& number) {
    		//注意要使用引用传递,否则无法修改number
    		bool isOverflow = false;//检测是否越界
    		int nTakeOver = 0;//存储进位
    		int nLength = number.size();
    		for (int i = nLength - 1; i >= 0; i--)
    		{
    			int nSum = number[i] - '0' + nTakeOver;
    			if (i == nLength - 1)
    				//如果是第一位,进位
    			{
    				nSum++;
    			}
    			if (nSum >= 10)//有进位
    			{
    				if (i == 0)
    					//如果是最高位有进位,说明超过了给定得到最大值,越界
    				{
    					isOverflow = true;
    				}
    				else
    				{
    					nTakeOver = 1;
    					number[i] = nSum - 10 + '0';//对第i位进行设置
    				}
    			}
    			else//没有进位
    				//设置第i位数字
    				//并直接跳出循环
    			{
    				number[i] = nSum + '0';
    				break;
    			}
    		}
    		return isOverflow;
    	}
    	void saveNumber(string number)
    		//由于此处输出,不需要修改number,因此不需要加引用
    	{
    		string s = "";
    		bool isBegin0 = true;
    		string::iterator it = number.begin();
    		while (it != number.end())
    		{
    			if (isBegin0 && *it != '0') isBegin0 = false;
    			if (!isBegin0)
    			{
    				s += *it;
    			}
    			it++;
    		}
    		int num = stoi(s);
    		res.push_back(num);
    	}
    };
    

    面试题18. 删除链表的节点

    给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

    注意:此题对比原题有改动

    示例 1:
    输入: head = [4,5,1,9], val = 5
    输出: [4,1,9]
    解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
    
    
    示例 2:
    输入: head = [4,5,1,9], val = 1
    输出: [4,5,9]
    解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
     
    
    说明:
    题目保证链表中节点的值互不相同
    若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点
    

    中规中矩:

    class Solution {
    public:
        ListNode* deleteNode(ListNode* head, int val) {
            if(head==NULL)     return NULL;
            if(head->val==val) return head->next;
    
            ListNode* pNode = head;
            while(pNode->next){
                if(pNode->next->val==val){
                    pNode->next = pNode->next->next;
                    break;
                }
                pNode=pNode->next;
            }
            return head;
        }
    };
    

    【原题】
    而原题比这道题更有味道,至少让我学会了还能直接根据给定的节点指针,无需遍历,以O(1)的时间复杂度删除这个节点,具体说来就是:
    当我们想删除一个节点时,并不一定要删除这个节点本身。可以先把下一个节点的内容复制出来覆盖被删除节点的内容,然后把下一个节点删除。

    struct ListNode {
    	int val;
    	ListNode* next;
    };
    
    void DeletedNode(ListNode** pListHead, ListNode* pToBeDeleted) {
    	if (!pListHead || !pToBeDeleted) {
    		return;
    	}
    
    	//要删除的节点不是尾结点
    	if (pToBeDeleted->next != NULL) {
    		//绝妙
    		//找到待删节点,与其下一节点进行数据交换,删除下一节点
    		ListNode* pNext = pToBeDeleted->next;
    		pToBeDeleted->val = pNext->val;
    		pToBeDeleted->next = pNext->next;
    		//释放内存
    		delete pNext;
    		pNext = NULL;
    	}
    	//要删除的节点是尾结点,但是链表只有一个节点(此时,要删除的节点也是头节点)
    	else if (*pListHead == pToBeDeleted) {
    		delete pToBeDeleted;
    		pToBeDeleted = NULL;
    		*pListHead = NULL;
    	}
    	//要删除的节点是尾结点,且链表中有多个节点(此时,要删除的节点不是头节点)
    	else {
    		ListNode* pNode = *pListHead;
    		while (pNode->next != pToBeDeleted) {
    			pNode = pNode->next;
    		}
    		pNode->next = NULL;
    		delete pToBeDeleted;
    		pToBeDeleted = NULL;
    	}
    }
    

    【原题-引申的题目二】
    删除链表中的重复节点们,遇到重复节点,一个不留,斩草除根

    struct ListNode {
    	int val;
    	ListNode* next;
    };
    
    
    void DeleteDuplication(ListNode** pHead) {//这里不可写成ListNode* pHead?
    	//如果指向头节点的指针为空
    	if (pHead == NULL || *pHead == NULL) {
    		return;
    	}
    	ListNode* pPreNode = NULL;
    	ListNode* pNode = *pHead;
    	while (pNode != NULL) {
    		ListNode* pNext = pNode->next;
    		bool needDelete = false;
    		if (pNext != NULL && pNext->val == pNode->val) {
    			needDelete = true;
    		}
    		if (!needDelete) {//如果当前pNode节点不需要被删除,那么继续往下个节点遍历
    			pPreNode = pNode;
    			pNode = pNode->next;
    		}
    		else {//如果当前pNode节点需要被删除
    			int value = pNode->val;
    			ListNode* pToBeDel = pNode;
    			while (pToBeDel != NULL && pToBeDel->val == value) {//一直删除,直到链表为空,或下个节点的值不等于这个数字
    				pNext = pToBeDel->next;
    				delete pToBeDel;
    				pToBeDel = pNext;
    			}
    
    			if (pPreNode == NULL) {//如果下个节点为空,说明删到链表尾了
    				*pHead = pNext;
    			}
    			else {//如果下个节点并不为空,却跳出了循环,说明下个节点值不等于value
    				pPreNode->next = pNext;
    			}
    			pNode = pNext;//从这里开始继续寻找重复的节点
    		}
    	}
    }
    

    面试题19. 正则表达式匹配(难掉牙了)

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

    示例 1:
    输入:
    s = "aa"
    p = "a"
    输出: false
    解释: "a" 无法匹配 "aa" 整个字符串。
    
    
    示例 2:
    输入:
    s = "aa"
    p = "a*"
    输出: true
    解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
    
    
    示例 3:
    输入:
    s = "ab"
    p = ".*"
    输出: true
    解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
    
    
    示例 4:
    输入:
    s = "aab"
    p = "c*a*b"
    输出: true
    解释: 因为 '*' 表示零个或多个,这里 'c'0, 'a' 被重复一次。因此可以匹配字符串 "aab"。
    
    
    示例 5:
    输入:
    s = "mississippi"
    p = "mis*is*p*."
    输出: false
    s 可能为空,且只包含从 a-z 的小写字母。
    p 可能为空,且只包含从 a-z 的小写字母以及字符 .*,无连续的 '*'

    法一:

    class Solution {
    public:
        bool isMatch(string s, string p) {
            if(p.empty()) return s.empty();
            if(p[1] == '*'){
                return isMatch(s, p.substr(2)) || //目标字符串全都能匹配上模板从位置2往后的内容
                (!s.empty() && (s[0] == p[0] || p[0] == '.')) && //目标字符串不能为空 && (两串的首字母相同,或者模板为'.')
                isMatch(s.substr(1), p);//从目标字符串的第2个位置开始的子串,要能和整个模板匹配上
            }
            else{
                return !s.empty() && //目标字符串不能为空,不然不管模板是啥都没办法匹配
                (s[0] == p[0] || p[0] == '.') && //如果第二个字符不为*,那么第一个字符务必相同,或者模板为'.'
                (isMatch(s.substr(1), p.substr(1)));//且,从位置1开始的字符串(包括位置1字符)比如能满足匹配要求
            }
        }
    };
    

    法二:

    class Solution {
    public:
        bool isMatch(string s, string p) {
            s=" "+s;//防止该案例:""
    "c*"
            p=" "+p;
            int m=s.size(),n=p.size();
            bool dp[m+1][n+1];
            memset(dp,false,(m+1)*(n+1));
            dp[0][0]=true;
            for(int i=1;i<=m;i++){
                for(int j=1;j<=n;j++){
                    if(s[i-1]==p[j-1] || p[j-1]=='.'){
                        dp[i][j]=dp[i-1][j-1];
                    }
                    else if(p[j-1]=='*'){
                        if(s[i-1]!=p[j-2] && p[j-2]!='.')
                            dp[i][j]=dp[i][j-2];
                        else{
                            dp[i][j]=dp[i][j-1] || dp[i][j-2] || dp[i-1][j];
    
                        }
                    }
                }
            }
            return dp[m][n];
        }
    };
    

    面试题20. 表示数值的字符串

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

    class Solution {
    public:
        bool isNumber(string s) {
            s = trim(s);
            const char* str = s.c_str();//将字符串string转变为字符数组
            if(str==NULL){
                return false;
            }
            //遍历正负号和纯数字,停留在纯数字的尾后位置
            bool numeric = scanInteger(&str);
            // 如果当前str是'.',则接下来应该是数字的小数部分
            if(*str=='.'){
                ++str;
                numeric = scanUnsignedInteger(&str) || numeric;//如果有数字,遍历,并移动到纯数字的尾后位置
            }
            //看是否遇到'e'或'E'
            if(*str=='e' || *str=='E'){
                ++str;
                numeric = numeric && scanInteger(&str);
            }
            return numeric && *str=='';
        }
        bool scanUnsignedInteger(const char ** str){
            const char* before = *str;
            while(**str!='' && **str>='0' && **str<='9'){
                ++(*str);
            }
            return *str>before;//指针移动了位置,说明存在纯数字
        }
        bool scanInteger(const char** str){
            if(**str=='+' || **str=='-'){
                ++(*str);
            }
            return scanUnsignedInteger(str);
        }
        //去除字符串中的首尾空格
        std::string& trim(std::string &s) 
        {
            if (!s.empty()) 
            {
                s.erase(0,s.find_first_not_of(" "));
                s.erase(s.find_last_not_of(" ") + 1);
            }
            return s;
        }
    
    };
    

    面试题21. 调整数组顺序使奇数位于偶数前面

    输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

    示例:
    输入:nums = [1,2,3,4]
    输出:[1,3,2,4] 
    注:[3,1,2,4] 也是正确的答案之一。
     
    
    提示:
    1 <= nums.length <= 50000
    1 <= nums[i] <= 10000
    
    class Solution {
    public:
        vector<int> exchange(vector<int>& nums) {
            int n = nums.size();
            if(n<=0) return nums;
            int left = 0,right = nums.size()-1;
            while(left<right){//很关键,必须有,否则,只处理一对奇数偶数交换
                while(left<right && (nums[left]&0x1)!=0){//左边的指针遇到的是奇数就继续往后找
                    ++left;
                }
                while(left<right && (nums[right]&0x1)==0){//右边的指针遇到的是偶数就继续往前找
                    --right;
                }
                if(left<right){
                    std::swap(nums[left],nums[right]);
                }
            }
            return nums;
        }
    };
    

    解耦思想:利用解耦思想,拆分函数功能,将判断一个数字该在前面还是后面的函数,与拆分数组的函数解耦,提高代码复用性。涉及函数指针的使用。

    class Solution {
    public:
        vector<int> exchange(vector<int>& nums) {
            return ReOrder(nums,iseven);
        }
        vector<int> ReOrder(vector<int>& nums,bool (*func)(int)) {
            int n = nums.size();
            if(n<=0) return nums;
            int left = 0,right = nums.size()-1;
            while(left<right){//很关键,必须有,否则,只处理一对奇数偶数交换
                while(left<right && !func(nums[left])){//左边的指针遇到的是奇数就继续往后找
                    ++left;
                }
                while(left<right && func(nums[right])){//右边的指针遇到的是偶数就继续往前找
                    --right;
                }
                if(left<right){
                    std::swap(nums[left],nums[right]);
                }
            }
            return nums;
        }
        static bool iseven(int n){
            return (n&0x1)==0;
        }
    };
    

    防御性编程习惯

    编写鲁棒性强的代码,在函数开始的时候验证用户输入内容是否符合要求,对于不符合要求的输入也能合理处理,使各种情况都在程序员掌控之中。
    多问问:如果不…那么…,此类的问题。

    面试题22. 链表中倒数第k个节点

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

    示例:
    给定一个链表: 1->2->3->4->5, 和 k = 2.
    返回链表 4->5.
    
    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode(int x) : val(x), next(NULL) {}
     * };
     */
    class Solution {
    public:
        ListNode* getKthFromEnd(ListNode* head, int k) {
            if(head==NULL || k==0){//代码鲁棒性
                return NULL;
            }
            ListNode * pNode1 = head;
            //指针1先走k-1步
            for(int i=0;i<k-1;++i){
                if(pNode1->next != NULL){//代码鲁棒性
                    pNode1=pNode1->next;
                }else{
                    return NULL;
                }
            }
            //指针2初始化后开始和指针1一起走,直到指针1走到头
            ListNode * pNode2 = head;
            while(pNode1->next != NULL){
                pNode1 = pNode1->next;
                pNode2 = pNode2->next;
            }
            return pNode2;
        }
    };
    

    快慢指针拓展

    快慢指针的思想:当我们用一个指针遍历链表不能解决问题的时候,可以尝试用两个指针遍历链表。可以让一个指针遍历速度快一些(比如一次在链表中走两步),或者让它先在链表上走上若干步。

    利用快慢指针的思想,求链表的中间节点也就不是难事:定义两个指针,同时从表头出发,一个指针一次走一步,另一个指针一次走两步。当走的快的指针走到链表尾后时,走得慢的指针恰好在链表中间。

    141. 环形链表

    给定一个链表,判断链表中是否有环。

    为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

    示例 1:
    
    输入:head = [3,2,0,-4], pos = 1
    输出:true
    解释:链表中有一个环,其尾部连接到第二个节点。
    
    
    示例 2:
    
    输入:head = [1,2], pos = 0
    输出:true
    解释:链表中有一个环,其尾部连接到第一个节点。
    
    
    示例 3:
    
    输入:head = [1], pos = -1
    输出:false
    解释:链表中没有环。
    

    思路:
    可以用快慢指针解决问题。定义两个指针,一个指针一次走一步,另一个指针一次走两步。如果走的快的指针追上了走得慢的指针,说明链表包含环。

    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode(int x) : val(x), next(NULL) {}
     * };
     */
    class Solution {
    public:
        bool hasCycle(ListNode *head) {
            if(head==NULL){//链表为空,不存在环
                return false;
            }
    
            ListNode* pSlow = head->next;
            if(pSlow==NULL){//链表只有一个节点时,不存在环
                return false;
            }
            ListNode* pFast = pSlow->next;//初始值不能和慢指针一样
            while(pFast!=NULL && pSlow!=NULL){
                if(pFast==pSlow){//判断快慢指针是否相等,如果相等,说明存在环
                    return true;
                }
    
                pSlow = pSlow->next;//快慢指针一起走路
                pFast = pFast->next;//快慢指针一起走路
    
                if(pFast!=NULL){//快指针每次比慢指针多走一步
                    pFast=pFast->next;
                }
            }
            return false;
        }
    };
    

    142. 环形链表 II

    给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

    为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

    说明:不允许修改给定的链表。

    示例 1:
    
    输入:head = [3,2,0,-4], pos = 1
    输出:tail connects to node index 1
    解释:链表中有一个环,其尾部连接到第二个节点。
    
    
    示例 2:
    
    输入:head = [1,2], pos = 0
    输出:tail connects to node index 0
    解释:链表中有一个环,其尾部连接到第一个节点。
    
    
    示例 3:
    
    输入:head = [1], pos = -1
    输出:no cycle
    解释:链表中没有环。
    
    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode(int x) : val(x), next(NULL) {}
     * };
     */
    class Solution {
    public:
        ListNode *detectCycle(ListNode *head) {
            ListNode* meetingNode = MeetingNode(head);
            if(meetingNode==NULL){//不存在环
                return NULL;
            }
            //获得环中节点的数目
            int count = 1;
            ListNode* pNode1 = meetingNode;
            while(pNode1->next != meetingNode){
                pNode1 = pNode1->next;
                ++count;
            }
            //先移动指针1,次数为环中节点数目
            pNode1 = head;
            for(int i=0;i<count;++i){
                pNode1=pNode1->next;
            }
            //再移动指针1和指针2
            ListNode* pNode2 = head;
            while(pNode1!=pNode2){
                pNode1=pNode1->next;
                pNode2=pNode2->next;
            }
            return pNode1;
        }
        ListNode* MeetingNode(ListNode* pHead){
            if(pHead==NULL){return NULL;}
    
            ListNode* pSlow = pHead->next;
            if(pSlow == NULL){
                return NULL;
            }
            ListNode* pFast = pSlow->next;
            while(pFast!=NULL && pSlow!=NULL){
                if(pSlow==pFast){
                    return pFast;
                }
                pSlow = pSlow->next;
                pFast = pFast->next;
                if(pFast!=NULL){
                    pFast = pFast->next;
                }
            }
            return NULL;
        }
    };
    

    面试题24. 反转链表

    定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

    示例:
    
    输入: 1->2->3->4->5->NULL
    输出: 5->4->3->2->1->NULL
     
    
    限制:
    
    0 <= 节点个数 <= 5000
    

    处理这个问题的时候,要注意我们需要知道3个关键:
    (1)当前节点本身
    (2)当前节点的上一个节点(要指向的目标)
    (3)当前节点的下一个节点(防止链表断裂)
    完成3项检查:
    (1)输入链表头指针为空,或整个链表只有一个节点时,程序的鲁棒性
    (2)反转后不要出现断裂
    (3)返回的反转后的头节点应该是原始链表的尾节点

    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode(int x) : val(x), next(NULL) {}
     * };
     */
    class Solution {
    public:
        ListNode* reverseList(ListNode* head) {
            ListNode* pReversedHead = NULL;//定义反转后的头节点
            ListNode* pNode=head,*pPrev=NULL;//定义当前节点指针(并初始化为头指针),和前一个节点的指针
            while(pNode!=NULL){//当前节点不为空,才进行下面的步骤,直到它为空
                ListNode* pNext = pNode->next;//记录下一个节点指针
                if(pNext==NULL){//到了尾节点了
                    pReversedHead = pNode;
                }
                pNode->next = pPrev;//反转的关键一步
                pPrev = pNode;//往后走一步
                pNode = pNext;//往后走一步
            }
            return pReversedHead;
        }
    };
    

    面试题25. 合并两个排序的链表

    输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

    示例1:
    
    输入:1->2->4, 1->3->4
    输出:1->1->2->3->4->4
    限制:
    
    0 <= 链表长度 <= 1000
    
    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode(int x) : val(x), next(NULL) {}
     * };
     */
    class Solution {
    public:
        ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
            //解决空表的情况,一个表为空,不需要合并,直接返回另一个表即可
            if(l1==NULL){
                return l2;
            }else if(l2==NULL){
                return l1;
            }
            ListNode* pMergeHead=NULL;
            if(l1->val < l2->val){
                pMergeHead = l1;
                pMergeHead->next = mergeTwoLists(l1->next,l2);
            }else{
                pMergeHead = l2;
                pMergeHead->next = mergeTwoLists(l1,l2->next);
            }
            return pMergeHead;
        }
    };
    

    面试题26. 树的子结构

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

    B是A的子结构, 即 A中有出现和B相同的结构和节点值。

    例如:
    给定的树 A:
    
         3
        / 
       4   5
      / 
     1   2
    给定的树 B:
    
       4 
      /
     1
    返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
    
    示例 1:
    
    输入:A = [1,2,3], B = [3,1]
    输出:false
    示例 2:
    
    输入:A = [3,4,5,1,2], B = [4,1]
    输出:true
    限制:
    
    0 <= 节点个数 <= 10000
    
    /**
     * Definition for a binary tree node.
     * struct TreeNode {
     *     int val;
     *     TreeNode *left;
     *     TreeNode *right;
     *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     * };
     */
    class Solution {
    public:
        bool isSubStructure(TreeNode* A, TreeNode* B) {
            bool result = false;
            if(A!=NULL && B!=NULL){
                if(A->val==B->val){
                    result = DoesTree1HaveTree2(A,B);
                }
                if(!result){
                    result = isSubStructure(A->left,B);
                }
                if(!result){
                    result = isSubStructure(A->right,B);
                }
            }
            return result;
        }
    
        bool DoesTree1HaveTree2(TreeNode* A,TreeNode* B){
            if(B==NULL){
                return true;
            }
            if(A==NULL){
                return false;
            }
            if(A->val != B->val){
                return false;
            }
            return DoesTree1HaveTree2(A->left,B->left) && DoesTree1HaveTree2(A->right,B->right);
        }
    };
    

    总结

    编程时,注意代码规范性、完整性、鲁棒性:

    (1)确保规范性:书写清晰,布局清晰,命名合理

    (2)确保完整性:编程前,全面考虑所有可能的输入,确保完成基本功能,考虑边界条件,做好错误处理

    (3)增强鲁棒性:采取防御性编程,处理无效输入

  • 相关阅读:
    MySQL 日志管理
    nginx 日志分割
    Canvas 动态小球重叠效果
    Canvas制作动态进度加载水球
    js 多张爆炸效果轮播图
    js 多张图片加载 环形进度条
    INSTALL_FAILED_CONFLICTING_PROVIDER
    安卓 文件管理器 各种应用 源码
    android 静音
    android studio 查看大纲
  • 原文地址:https://www.cnblogs.com/dindin1995/p/13059073.html
Copyright © 2020-2023  润新知