剑指offer刷题笔记1

  • 55 二叉树的深度【BFS】【DFS】
  • 56 数字在升序数组中出现的次数 【二分查找】
  • 24 反转链表 【递归】【链表】
  • 4 二维数组中的查找【二分查找】
  • 25 合并两个排序的链表 【链表】【递归】

55 二叉树的深度【BFS】【DFS】

题目:

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度,根节点的深度视为 1 。

解题思路:

1)利用深度优先搜索的思想,利用递归实现,获得二叉树的深度。同时递归就可以看作是栈的实现, 当函数一层层调用自己时就是入栈,返回结果时就是出栈

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    // 深度优先搜索的思想,用递归方法实现
    int TreeDepth(TreeNode* pRoot) {
        if(pRoot == NULL) return 0;
        cout << pRoot->val << endl; // 深度优先遍历树
        int left_depth = TreeDepth(pRoot->left);
        int right_depth = TreeDepth(pRoot->right);
        
        int depth = max(left_depth, right_depth) + 1;
        return depth;
    } 
};

2)采用广度优先搜索思想,用队列queue实现

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    // 利用Queue,采用广度优先搜索的思想得到树的深度
    // 空间复杂度0(N), 时间复杂度O(N)
    int TreeDepth(TreeNode* pRoot) {
        queue<TreeNode*> q;
        int level = 0;
        if(pRoot != NULL){ q.push(pRoot); }
        while(!q.empty()){
            int size = q.size();
            while(size--){
                if(q.front()->left != NULL){ q.push(q.front()->left); }
                if(q.front()->right != NULL){ q.push(q.front()->right); }
                q.pop();
            }
            level++;
        }
        return level;
    } 
    
    
};

56 数字在升序数组中出现的次数:【二分查找】

题目:

给定一个长度为 n 的非降序数组和一个非负数整数 k ,要求统计 k 在数组中出现的次数

解题思路1(我的):

利用二分查找,找到k在数组中的位置,然后向前和后循环,统计k出现次数
代码:

class Solution {
public:
    // 1.首先利用二分查找 找到 k 在数组中的位置
    int find(const vector<int> data ,const int &k, const int &low, const int& high){
        
        if(low == high) return -1;
        int position = -1;
        int middle = (low + high) / 2;
        
        if(k == data[middle]){
            return middle;
        }else if(k < data[middle]){
            position = find(data, k, low, middle);
        } else{
            position = find(data, k, middle + 1, high);
        }
        return position;
    }
    
    int findCount(const vector<int> &data ,const int &k, const int& posi){
        int sum = 1;
        int toLeft = posi;
        int toRight = posi;
        while(data[--toLeft] == k){
            sum += 1;
        }
        while(data[++toRight] == k){
            sum += 1;
        }
        return sum;
    }
    
    int GetNumberOfK(vector<int> data ,int k) {
        int position = find(data, k, 0, data.size());
        if(position == -1) {
            cout << "数组中不存在" << k << endl;
            return 0;
        }
        int count = findCount(data, k, position);
        return count;
    }
};

解题思路2(官方):

利用二分查找找到k的下界和上界,最后上界减去下界就是结果

24 反转链表 【递归】【链表】

题目:

给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。

解题思路1:

从头到尾遍历一遍结点,进行翻转
代码:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
        
        ListNode* pCurrent = NULL;      // 指向当前节点
        ListNode* pPrev = NULL;         // 存前一个指针
        
        while(pHead != NULL){
            pCurrent = pHead;
            pHead = pHead->next;
            pCurrent->next = pPrev;
            pPrev = pCurrent;
        }
        return pCurrent;
    }  */
};

解题思路2:

递归进行翻转

ListNode* ReverseList(ListNode* pHead) {
        if(pHead == NULL || pHead->next == NULL) return pHead;
        ListNode* pReversedHead = ReverseList(pHead->next);      // 指向当前节点
        
        pHead->next->next = pHead;
        pHead->next=NULL;
        
        return pReversedHead;
}  

我的总结:

一开始我想用栈对链表进行反转,但指针的关系被我弄得有些很混乱,而且用栈的空间复杂度是O(N). 其实链表的反转不需要用堆栈,就从头到尾把链表遍历一遍,反转就行。可以用递归,或者非递归的方法。

4 二维数组中的查找【二分查找】

题目:

在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]]
给定 target = 7,返回 true。
给定 target = 3,返回 false。

解题思路:

利用二分查找,由于数组是按行,按列递增的,要找最右上角或者左下角的元素,判断之后才能有分支。例如target设置为最右上角的原素9,这样比较后,若小于9就将target向左移动,若大于9就将target向下移动

11 旋转数组的最小数组 【二分查找】

题目:

有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。

数据范围:1 \le n \le 100001≤n≤10000,数组中任意元素的值: 0 \le val \le 100000≤val≤10000
要求:空间复杂度:O(1)O(1) ,时间复杂度:O(logn)O(logn)

解题思路:

利用二分查找

代码:

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        
        int start = 0;
        int end = rotateArray.size() - 1;
        int middle = (start + end / 2);
        
        
        while(start != end){
            if( rotateArray[(start+end)/2] > rotateArray[end] ) {
                start = (start+end) / 2 + 1;
            } else if( rotateArray[(start+end)/2] < rotateArray[end] ) {
                end = (start+end) / 2;
            } else{
                end -= 1;
            }
            
        }
        
        return rotateArray[start];
        
        
        // 二分法用中间的middle值和end端点作比较,若:
        // 1. 中间值 > end值, 最小值一定在右边
        // 2. 中间值 < end值, 最小值一定在左边
        // 3. 中间值 == end值,最小值即可能在左边,也可能在右边,因此将end减去1,直到start == end时返回
        // [1, 0, 1, 1, 1, 1, 1]
        // [1, 1, 1, 1, 1, 0, 1]
        
    }
};

25 合并两个排序的链表 【链表】【递归】

题目:

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

解题思路:

就是比较两个头节点哪个小,就先接入哪个节点。也可以用递归方法实现。
用递归方法需要考虑好 结束条件 和 缩小递归空间。需要注意的是创建单链表是需要自己创建一个虚拟的头节点pVHead,返回结果时返回pVHead->next, 这样链表里每个节点都有一个头节点

代码:

迭代法:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        ListNode* pResult = new ListNode(-1);
        ListNode* prev = pResult;
        while(pHead1 != NULL && pHead2 != NULL){
            if(pHead1->val < pHead2->val) {
                prev->next = pHead1;
                pHead1 = pHead1->next;
                prev = prev->next;
            } else{
                prev->next = pHead2;
                pHead2 = pHead2->next;
                prev = prev->next;
            }
        }
        prev->next = pHead1 ? pHead1:pHead2;
        return pResult->next;
    }
};

77 按之字形顺序打印二叉树 【二叉树】【队列】【栈】

题目:

给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替)

解题思路:

其实就是二叉树的层级遍历

代码:

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    // 问题在于怎么判断该层数的遍历顺序:用一个level计数变量判断奇偶
    // 以及如何实现该层的遍历顺序: 用vector的向后插入和向前插入实现,push_back() 和 insert() 函数
    // 空间复杂度 O(N),时间复杂度O(N)
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int>> result;
        if(pRoot == NULL) return result;
        queue<TreeNode*> q;
        q.push(pRoot);
        int level = 0;     // 用来判断当前层数是正序输出 还是倒序输出
        vector<int> tmp;
        
        while(!q.empty()){
            int size = q.size();
            if(level % 2 == 1) //当level为奇数时,此层需要倒序遍历
            {
                while(size--){
                    if(q.front()->left != NULL) q.push(q.front()->left);
                    if(q.front()->right != NULL) q.push(q.front()->right);
                    tmp.insert(tmp.begin(), q.front()->val);  // 在vector tmp的头部插入数据
                    q.pop();
                }
                
            } else {  //正序遍历
                while(size--){
                    if(q.front()->left != NULL) q.push(q.front()->left);
                    if(q.front()->right != NULL) q.push(q.front()->right);
                    tmp.push_back(q.front()->val);
                    q.pop();
                }
            }
            
            result.push_back(tmp);
            level += 1;
            tmp.clear();
        }
        
        return result;
    }
    
};

JZ35 复杂链表的复制

题目:

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。
在这里插入图片描述

解题思路:

  1. 用map将原链表节点地址 和 新链表映射地址存储好,创建完新链表后 再一一进行还原
  2. 将新节点插入的形式创建到原链表中,用双指针复制好random映射关系后,之后再用双指针把链表拆开

代码:

思路1:

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    // 解题思路1:
    // 我都没看懂这个题深拷贝是什么意思, 就是要在堆区重新申请数据
    // 原链表本身还好复制,但这个random的指向关系,如何复制? 我用了一个map来存储原链表的映射关系

    
    RandomListNode* Clone(RandomListNode* pHead) {
        
        
        RandomListNode* vHead = new RandomListNode(0); // 创建一个虚拟的头节点
        RandomListNode* result = vHead;
        map<RandomListNode*, RandomListNode*> m;
        while(pHead != NULL){
            RandomListNode* tmp = new RandomListNode(pHead->label); // new操作返回一个该类型数据创建在堆区的地址
            vHead->next = tmp;
            tmp->random = pHead->random;  // 讲原先的random指针关系记录
            m.insert(pair<RandomListNode*, RandomListNode*>( pHead, tmp )); // 将原地址和新地址的映射存到map里
         
            pHead = pHead->next;
            vHead = vHead->next;
        }
        
        vHead = result->next;
        while(vHead != NULL){
            if(vHead->random != NULL){
                vHead->random = m.find(vHead->random)->second;
            }
            vHead = vHead->next;
        }
        
        
        return result->next; 
    }
};

思路2:

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    //  需要注意的点:
    // 1)当newP指向NULL时,不能再调用newP->next了
    // 2)注意循环中要把指针往后移
    // 3)continue会直接跳出一层while循环
    

    
    RandomListNode* Clone(RandomListNode* pHead) {
        if(pHead == NULL) return NULL;
        
        RandomListNode* pointer = pHead;
        while(pointer != NULL){
            RandomListNode* tmp = new RandomListNode(pointer->label); // 1. 创建新的节点
            tmp->next = pointer->next;
            pointer->next = tmp;  // 2. 新节点插入到链表中
            pointer = tmp->next;
        }
        
        // 3. 复制映射关系
        RandomListNode* oldP = pHead;
        RandomListNode* newP = pHead->next;
        while(oldP != NULL){
            if(oldP->random != NULL){
                newP->random = oldP->random->next;
            }
            oldP = oldP->next->next;
            if(newP->next != NULL){
                newP = newP->next->next;
            }
        }
        
        oldP = pHead;
        newP = pHead->next;
        RandomListNode* result = pHead->next;
        while(oldP != NULL){
            oldP->next = oldP->next->next;
            oldP = oldP->next;
            if(newP->next != NULL){
                newP->next = newP->next->next;
                newP = newP->next;
            }
        }
        
        return result;
    }
};

思路2精简版代码:

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    
    RandomListNode* Clone(RandomListNode* pHead) {
        if(pHead == NULL) return NULL;
        
        unordered_map<RandomListNode*, RandomListNode*> m;
        RandomListNode* cur = pHead;
        while(cur != NULL){
            m[cur] = new RandomListNode(cur->label); //1. 建立老节点和新建的节点之间的映射关系
            cur = cur->next;
        }
        
        cur = pHead;
        while(cur != NULL){
            m[cur]->next = m[cur->next];     // 2. 连接好新建的结点的链表关系
            m[cur]->random = m[cur->random]; // 3. 创新新的链表的random映射关系
            cur = cur->next;
        }
        
        return m[pHead];  // 4. 返回旧的头指针对应的新的头指针
    }
};

JZ76 删除链表中重复的结点

题目

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

数据范围:链表长度满足 0 \le n \le 1000 \0≤n≤1000 ,链表中的值满足 1 \le val \le 1000 \1≤val≤1000

进阶:空间复杂度 O(n)\O(n) ,时间复杂度 O(n) \O(n)

例如输入{1,2,3,3,4,4,5}时,对应的输出为{1,2,5},对应的输入输出链表如下图所示:
在这里插入图片描述

解题思路:

  1. 创建虚拟头节点,直接遍历链表
  2. 递归实现

代码:

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    // 处理特殊情况比较头疼,比如{1,1} 和 {1}, 还是得用虚拟头节点
    // 时间复杂度 O(N), 空间复杂度O(1)
    ListNode* deleteDuplication(ListNode* pHead) {
        if(pHead == NULL) return NULL;
        
        ListNode* vHead = new ListNode(-1);
        vHead->next = pHead;    // 1 创建虚拟头节点
        
        ListNode* lastNode = vHead;
        ListNode* cur = vHead;
        while(cur != NULL && cur->next != NULL){
            
            if(cur->val == cur->next->val){
                int value = cur->val;
                
                while(cur != NULL && cur->val == value){
                    
                    cur = cur->next;
                }
                lastNode->next = cur;
                continue;
            }
            lastNode = cur;
            cur = cur->next;
        }
        
        return vHead->next;
    }
};

JZ54 二叉搜索树的第K个节点

题目

给定一棵结点数为n 二叉搜索树,请找出其中的第 k 小的TreeNode结点值。
1.返回第k小的节点值即可
2.不能查找的情况,如二叉树为空,则返回-1,或者k大于n等等,也返回-1
3.保证n个节点的值不一样

思路:

先用中序遍历将树的节点放入一个数组,之后返回第K个值

代码

1、递归实现中序遍历

/**
 * struct TreeNode {
 *	int val;
 *	struct TreeNode *left;
 *	struct TreeNode *right;
 *	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 * };
 */
class Solution {
public:
    // 把树中序遍历一遍存入数组
    void MiddleOrder(TreeNode* proot, vector<int>& numbers){
        if(proot == NULL) return;
        MiddleOrder(proot->left, numbers);
        numbers.push_back(proot->val);
        MiddleOrder(proot->right, numbers);
    }
    
    int KthNode(TreeNode* proot, int k) {
        // write code here
        if(proot == NULL or k == 0) return -1;
        vector<int> numbers;
        MiddleOrder(proot, numbers);
        if(k > numbers.size()) return -1;
        return numbers[k-1];
    }
};

2、栈实现中序遍历

/**
 * struct TreeNode {
 *	int val;
 *	struct TreeNode *left;
 *	struct TreeNode *right;
 *	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 * };
 */
class Solution {
public:
    // 把树中序遍历一遍存入数组
    // 用栈实现中序遍历
    int KthNode(TreeNode* proot, int k) {
        // write code here
        if(proot == NULL or k == 0) return -1;
        stack<TreeNode*> s;
        TreeNode* tmp = NULL;
        s.push(proot);
        int i = 0;
        
        while(!s.empty()){
            while(s.top()->left != NULL){    // 1.把当前节点最小的左节点插入到栈顶
                s.push(s.top()->left);
            }
            
            while(!s.empty()){  
                tmp = s.top();
                s.pop();
                i += 1;
                if( i == k) return tmp->val;
                if(tmp->right != NULL){  // 2. 当前节点存在右子树时,把右子树放到栈顶,
                                         // break调,返回到(1),把右子树的最小左节点push到栈中
                    s.push(tmp->right);
                    break;
                }
            }
        }
        return -1;
    }
};

JZ26 树的子结构 【递归】

题目

输入两棵二叉树A,B,判断B是不是A的子结构。(我们约定空树不是任意一个树的子结构)
假如给定A为{8,8,7,9,2,#,#,#,#,4,7},B为{8,9,2},2个树的结构如下,可以看出B是A的子结构
在这里插入图片描述

解题思路:

递归思想,先用递归判断是否包含子树,再用递归判断是否子树相等。但要注意递归边界条件的判断

代码:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    // 思路:用递归的方式,递归的检查 前树的子树 是否包含 后树
    //      后用递归检查 前树的子树 和 后树 是否相等
    // 主要:递归比较两树是否相等时,注意边界条件,当后数的子节点为空时,前树子节点和后树子节点的比较就是true的
    
    bool isSame(TreeNode* pRoot1, TreeNode* pRoot2){
        
        // 如果pRoot2未空,pRoot1空了,或者两个树头节点不相等,返回false
        if(pRoot1 == NULL || pRoot1->val != pRoot2->val) return false;
        
        // 将左子树和右子树相等的初值设置为true
        bool left = true;
        bool right = true;
        
        // 如果后子树 存在左节点, 则有比较左子树的必要,若不存在就说明 前树 直接包含了 后树的 左子树
        if(pRoot2->left){
            left = isSame(pRoot1->left, pRoot2->left);
        }
        // 如果后子树 存在右节点, 则有比较右子树的必要,若不存在就说明 前树 直接包含了 后树的 右子树
        if(pRoot2->right){
            right = isSame(pRoot1->right, pRoot2->right);
        }
        return left && right;
    }
    
    
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
        // 但凡有一个树为空,返回false
        if(!pRoot1 || !pRoot2) return false;
        if(isSame(pRoot1, pRoot2)) return true;
        
        if(HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2)){
            return true;
        } else{
            return false;
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值