剑指 Offer 第三章总结与实现

3 高质量的代码

面试题16 数值的整数次方

题目描述

给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent。求 base 的 exponent 次方。不可使用库函数,不用考虑大数问题。

解题思路

考察代码的完整性,不能觉得它简单,要尽可能想的全面。

  1. base 是 0,或者 exponent 是0
  2. exponent 是一个负值
  3. 快速幂

Code

递归的进行快速幂:

class Solution {
public:
    double Power(double base, int exponent) {
        if (exponent == 0)
            return 1;
        if (exponent == 1)
            return base;
        if (base == 0)
            return 0;
        if (exponent < 0)
        {
            base = 1 / base;
            exponent = -exponent;
        }
        if (exponent & 1)
            return base * Power(base*base, exponent >> 1);
        else
            return Power(base*base, exponent >> 1);

    }
};

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

题目描述

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

解题思路

这里需要考虑大数的问题,因为当 n 稍微大一点的时候,比如 十几,用 int 就放不下了。这里可以使用回溯法的思想,利用 string 保存数字,一直去按顺序探索路径,每次探索的时候保留着前缀。

Code

void print(int n, string prefix){
    // 打印 n 位数
    char a = '0';
    if (n == 1){
        for (int i = 0; i < 10; i ++){
            cout << prefix << char(a + i) << " ";
        }
        return;
    }
    for (int i = 1; i < 10; i ++){
        // 在前面加一位数
        string new_prefix = char(a + i) + prefix;
        print(n-1, new_prefix);
    }
}

int main(){
    int n;
    string empty;
    while(cin >> n){
        for (int i = 1; i <= n; i++){
            print(i, empty);
        }
    }
    cout << endl;
    return 0;
}

面试题18.1 删除链表的节点

题目描述

在 O(1) 时间内删除链表节点,给定单向链表的头指针和一个节点指针,定义一个函数在 O(1) 时间内删除该节点。

解题思路

删除链表中的节点并不必须知道它的前一个节点,分两种情况考虑

  1. 要删除的节点不是尾节点,把它后面的节点值复制到当前节点,然后把后面的节点删除
  2. 如果是尾节点,没办法,只能从头到尾遍历一遍,时间复杂度为 O(n)
    算法的平均复杂度是 O(1)

面试题18.2 删除链表重复的节点

题目描述

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

解题思路

  1. 为了使得处理统一,建立一个空的头指针
  2. 遍历整个链表,遍历的同时保留住其上一个节点,当遇到某一个节点的值与其之后节点的值相同时,进入一个 while 循环,不断删除掉与这个值相同的节点

Code

class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        if (pHead == NULL) return pHead;
        ListNode* newHead = new ListNode(0);
        newHead -> next = pHead;
        ListNode* p = newHead -> next;
        ListNode* q = newHead;
        while( p != NULL){
            if (p -> next != NULL && p -> val == p -> next -> val){
                int t = p -> val; // found duplicates
                while(p != NULL && p -> val == t){
                    q -> next = p -> next;
                    p = q -> next;
                }
            }
            else{
                q = p;
                p = p -> next;
            }
        }
        return newHead -> next;
    }
};

面试题19 正则表达式匹配

题目描述

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

解题思路

这题不难,主要是需要考虑的东西太多。

  • 递归进行求解。
  • 先写停止条件
    • 如果 pattern 和 str 都为空,说明配对成功
    • 如果只有 pattern 为空,说明配对失败
    • (要注意,如果只有 str 为空,也是有可能配对成功的)
  • 如果 pattern 的第二个字符为 *
    • 如果 pattern 和 str 第一个字符相等,或者 pattern 第一个字符为 ‘.’,并且str 不为空,说明可以配对,这时可以
      • 让 pattern 往后走两个,或者
      • 让 str 往后走一个
    • 如果不等的话,只能让 pattern 往后走两个
  • 如果第二个字符不为 *
    • 如果 pattern 和 str 第一个字符相等,或者 pattern 第一个字符为 ‘.’,并且str 不为空,说明可以配对,这时让 str 和 pattern 同时走一步

Code

class Solution {
public:
    bool match(char* str, char* pattern)
    {
    	if (*pattern == '\0' && *str == '\0')
    		return true;
    	if (*pattern == '\0' && *str != '\0')
    		return false;
    	if (*(pattern + 1) != '*'){
    		if (*str == *pattern || (*pattern == '.' && *str != '\0'))
    			return match(str + 1, pattern + 1);
    		else
    			return false;
    	}
    	else{
    		if (*str == *pattern || (*pattern == '.' && *str != '\0')){
    			return match(str + 1, pattern) ||
    				match(str, pattern + 2);
    		}
    		else
    			return match(str, pattern + 2);
    	}
    }
};

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

题目描述

判断一个给定的字符串是不是一个有效的数字,比如,字符串 “+100”,“5e2”,"-123",“3.1416” 和 “-1E-16” 都表示数值。 但是 “12e”,“1a3.14”,“1.2.3”,“±5” 和 “12e+4.3” 都不是。

解题思路

用三个布尔变量,分别表示是否见过符号,小数点,以及 e,有以下的判断条件:

  • 不可以存在两个 e
  • e 之后不可以是空
  • 如果第二次出现符号,那么它之前一定是 e
  • 如果是第一次出现符号,但是它并不在字符串的开头,那么它之前也应该是 e
  • e 之后不可以接小数点,并且小数点不可以出现两次
  • 不可以有不合法的字符

Code

class Solution {
public:
    bool isNumeric(char* str) {
        // 标记符号、小数点、e是否出现过
        bool sign = false, decimal = false, hasE = false;
        for (int i = 0; i < strlen(str); i++) {
            if (str[i] == 'e' || str[i] == 'E') {
                if (i == strlen(str)-1) return false; // e后面一定要接数字
                if (hasE) return false;  // 不能同时存在两个e
                hasE = true;
            } else if (str[i] == '+' || str[i] == '-') {
                // 第二次出现+-符号,则必须紧接在e之后
                if (sign && str[i-1] != 'e' && str[i-1] != 'E') return false;
                // 第一次出现+-符号,且不是在字符串开头,则也必须紧接在e之后
                if (!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E') return false;
                sign = true;
            } else if (str[i] == '.') {
              // e后面不能接小数点,小数点不能出现两次
                if (hasE || decimal) return false;
                decimal = true;
            } else if (str[i] < '0' || str[i] > '9') // 不合法字符
                return false;
        }
        return true;
    }
};

面试题21

题目描述

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

解题思路

如果不考虑相对位置的话,可以利用两个指针,当前指针碰到偶数,后指针碰到奇数,交换两者的位置。

这里需要考虑相对的位置,那么只能够考虑别的方法。

  1. 可以通过插入排序的思想,去寻找待插入元素的位置。按顺序去插入,如果是奇数的话,插入到数组中奇数的最后(这个时候需要后面所有的偶数都往后搬),如果是偶数,插入到数组的最后面(不需要移动元素)。
  2. 另一种方法是,从前往后扫描元素,如果碰到了一个偶数,那么在它的后面找奇数,如果全部是偶数,说明任务完成。找到奇数以后,让这个奇数与它前面的元素交换位置,这里必须相邻的交换,不然会造成排序不稳定。
  3. 前两种方法时间复杂度都是 O(n^2),空间复杂度 O(1)。还有一种可以 O(n) 时间完成的方法,空间复杂度也是 O(n)。先遍历一遍数组,统计奇数的个数,然后新建一个数组,保留奇数偶数两个 idnex,奇数初始值为 1,偶数初始值为奇数的个数。然后将数组按照奇偶数对应的复制过去。

Code

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        for (int i = 0; i < array.size(); i ++){
        	if (array[i] % 2 == 1){
        		continue;
        	}
        	else{
        		//cound even number
        		bool exchange = false;
        		for(int j = i + 1; j < array.size(); j ++){
        			if (array[j] % 2 == 1){
        				// 只可以相邻的进行交换
        				swap(array[j - 1], array[j]);
        				exchange = true;
        				break;
        			}
        		}
        		if (exchange){
        			i --; // i 不变化,继续让后面的往前面换
        		}
        		else{
        			return;
        		}
        	}
        }
    }
};

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

题目描述

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

解题思路

用两个指针,第一个指针先往后走 k 步,然后两个指针一起走,当第一个指针走到头时,第二个指针指向的就是倒数第 k 个节点。需要注意以下的情况:

  • 当长度不到 k 的时候需要正确处理
  • 当 k = 0 时
  • 当链表指针为空时

Code

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
    	unsigned int p = 0;
    	if (pListHead == NULL || k == 0)
    		return NULL;
   		ListNode* t = pListHead;
    	while(t != NULL && p < k){
    		p ++;
    		t = t -> next;
    	}
    	if (p != k) return NULL;
    	ListNode* q = pListHead;
    	while(t){
    		t = t -> next;
    		q = q -> next;
    	}
    	return q;
    }
};

面试题23 链表中环的入口节点

题目描述

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

解题思路

设置快慢两个指针,快指针一次走两步,慢指针一次走一步。如果两个指针相遇,说明链表中是有环的。这时,让其中一个指针从头开始走,当两个指针再次相遇,这时就是链表中环的起始节点。

Code

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
    	ListNode* fast = pHead;
    	ListNode* slow = pHead;
    	while(fast != NULL && fast -> next != NULL){
    		fast = fast -> next -> next;
    		slow = slow -> next;
    		if(fast == slow)
    			break;
    	}
    	if (fast == NULL || fast -> next == NULL)
    		return NULL;
    	fast = pHead;
    	while(fast != slow){
    		fast = fast -> next;
    		slow = slow -> next;
    	}
    	return fast;
    }
};

面试题24 反转链表

题目描述

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

解题思路

  1. 直接使用链表的头插法即可,记得自己 new 的空间,要自己 delete 掉,不然会内存泄漏(被面试的时候教的。。。)
  2. 第二种思路,递归实现。先反转当前节点的下一个节点。反转完成以后,返回了链表头,当前节点的下一个节点变成了链表的末尾。然后把当前节点续到链表末尾上。
  3. 第三种思路,直接修改指针。将当前节点的下一个节点的 next 指向当前节点。

Code

第一种思路:

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
    	ListNode* newHead = new ListNode(0);
    	while(pHead){
    		ListNode* t = pHead -> next;
    		pHead -> next = newHead -> next;
    		newHead -> next = pHead;
    		pHead = t;
    	}
    	ListNode* t = newHead -> next;
    	delete newHead;
    	return t;
    }
};

第二种思路:

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
    	if(pHead == NULL || pHead -> next == NULL) return pHead;
    	ListNode* newHead = ReverseList(pHead -> next);
    	pHead -> next -> next = pHead;
    	pHead -> next = NULL;

    	return newHead;
    }
};

第三种思路:

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
    	if (pHead == NULL || pHead -> next == NULL){
    		return pHead;
    	}
    	ListNode* p = pHead;
    	ListNode* q = p -> next;
    	pHead -> next = NULL;
    	while(q){
    		ListNode* r = q -> next;
    		q -> next = p;
    		p = q; q = r;
    	}
    	return p;
    }
};

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

题目描述

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

解题思路

直接归并排序。迭代比较好实现,递归的思想很巧妙。

Code

第一种解法,迭代:

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode* newHead = new ListNode(0);
        ListNode* p = newHead;
        while(pHead2 != NULL && pHead1 != NULL){
        	ListNode* t;
        	if(pHead1 -> val <= pHead2 -> val){
        		t = pHead1;
        		pHead1 = pHead1 -> next;
        	}
        	else{
        		t = pHead2;
        		pHead2 = pHead2 -> next;
        	}
        	t -> next = NULL;
        	p -> next = t;
        	p = p -> next;
        }
        if(pHead1){
        	p -> next = pHead1;
        }
        if(pHead2){
        	p -> next = pHead2;
        }
        p = newHead -> next;
        delete newHead;
        return p;
    }
};

第二种解法,递归:

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

面试题26 树的子结构

题目描述

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。需要注意子结构的定义,一棵树是另外一棵树的一部分即可,不一定要包含叶节点。

解题思路

递归的进行,在 A 树上的每个节点判断,在这个节点上,B 树是不是 A 树的子树。这个判断也是一个递归的过程。

Code

class Solution {
public:

	bool isSubTree(TreeNode* root1, TreeNode* root2){
		if (root2 == NULL) return true;
		if (root1 == NULL) return false;
		if (root1 -> val != root2 -> val)
			return false;
		return isSubTree(root1 -> left, root2 -> left) && isSubTree(root1 -> right, root2 -> right);
	}

    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
    	if(pRoot1 == NULL || pRoot2 == NULL)
    		return false;
    	if(isSubTree(pRoot1, pRoot2))
    		return true;
    	return HasSubtree(pRoot1 -> left, pRoot2) || HasSubtree(pRoot1 -> right, pRoot2);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值