手撕 《劍指offer》

140 阅读12分钟

1. 

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 


问题:

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。 来源:力扣(LeetCode) 


总结:数组为空和长度为0是两个事情


//如何从ArrayList转化为整形数组,toArray()一直报错耶

重载T toArray(T a)这个一直报错

数组的创建,

一开始不给他分配多大可以吗?然后后面再循环一个一个给他加,试过了,我的报错,哭了

//也不能随机从数组里面抽取某一个元素


解析:通过Index 和 LastIndexOf()判断数组元素是否有相同

java,for循环,多个变量一起循环:for(a=1,b=2,c=3;a<b;a++,b--,c++){}逗号隔开就行。


面向对象的数组:

增删改查


查找算法:

线性查找: 拿着照片遍历查找(效率低)

二分法:(数组是有序的)


栈:

有点像汉诺塔,还有像 弹夹的例子。

用数组实现:1.把栈顶看成数组末尾。

增加和删除都在数组末尾进行

队列:(银行排队原理,先来先到)

代码实现: 

1. 从队头出(数组的第0个元素取出)

2.创建新的数组(长度原来-1)


进入:

1.从数组的末尾进入 


单链表:

结构:数据+下一个节点的地址

自己写:

Node类。包含两个字段,Node类型的next, int 类型的data

功能:

1. 增加到末尾

  • 在这个方法里面新建Node类型的 current类型
  • while(死循环)  退出的因素,没有nextNode为空了/ 循环里面再新建nextNode字段=current.next
  • 然后将current设置为 为null的空节点,之后,把这个需要新增的元素,放在currentNode的nextNode字段下面

2.找到下一个元素、、


String 題目類:

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

思路:

1.創建新的一個stringbuffer str

2.判斷,儅charAt(“”)成立的時候,添加%20

3.else ,就把str[i]複製給新的str2

總結:

1.Stringbuffer 内存效率高,對他修改不會想STring類那樣創建新的對象,因此内存效率高

2.String buffer 有 CHarAt(“”) ,根據值找索引。 Index of()返回子字符串的索引

3. STRingbuffer replace , 將 一段位置標注出來(start,end)。用Str去替代


链表总结:

Java有链表类 ListNode ,可以直接新建 ListNode ln=new ListNode(5)

class ListNode {
    ListNode next;
    int val;
    ListNode(int x){
        val = x;
        next = null;
    }
}

 方法有 add.next()  isEmpty()

 属性 int value

题目一:

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

       思路:

       1. 迭代链表,将value装进stack

        2. 将stack  迭代pop给ArrayList


总结:

Stack 可以和ListNode一样创建,带式构造方法不带参数,需要导入包

ArrayList没有addFirst()方法

思路总结:

概念引入:假如有环,两个指针,一个快的pFast一个慢的pSLow。快的速度是慢的两倍

当相遇在C点的时候,让快指针重新回到起点,并且速度和慢指针一样,记为慢指针2。重新走过第一次慢指针的路径长度时,慢指针2和慢指针1在相遇在C点。但是因为他们速度相同,所以他们肯定在环的起始就相遇了。所以循环推出的条件是

(pSlow2=pSLow),否则他们就一直循环,返回相等的那个点




//先说个定理:两个指针一个fast、一个slow同时从一个链表的头部出发
//fast一次走2步,slow一次走一步,如果该链表有环,两个指针必然在环内相遇
//此时只需要把其中的一个指针重新指向链表头部,另一个不变(还在环内),
//这次两个指针一次走一步,相遇的地方就是入口节点。
//这个定理可以自己去网上看看证明。
public 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;
}
}

题目三:

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

思路:为了便于开始处理第一个元素(1),从第二个元素开始判断。加入标志性的头节点。

设置两个指针,pre表示符合要求的,next用来表示 用来工作的指针

非递归的代码:

1. 首先添加一个头节点,以方便碰到第一个,第二个节点就相同的情况

2.设置 pre ,last 指针, pre指针指向当前确定不重复的那个节点,而last指针相当于工作指针,一直往后面搜索

if (pHead==null || pHead.next==null){return pHead;}
ListNode Head = new ListNode(0);
Head.next = pHead;
ListNode pre = Head;
ListNode last = Head.next;
while (last!=null){
if(last.next!=null && last.val == last.next.val){
// 找到最后的一个相同节点
while (last.next!=null && last.val == last.next.val){
last = last.next;
}
pre.next = last.next;
last = last.next;
}else{
pre = pre.next;
last = last.next;
}
}
return Head.next;



站和队列:

题目1:

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。




//引用马客(Mark)的解题思路,马客没加注释,我用自己的理解加下注释,希望对你们有用,
//如有错误,见谅,我会及时修改。
//deque s中存储的是num的下标
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int> res;
deque<int> s;
for(unsigned int i=0;i<num.size();++i){
while(s.size() && num[s.back()]<=num[i])//从后面依次弹出队列中比当前num值小的元素,同时也能保证队列首元素为当前窗口最大值下标
s.pop_back();
while(s.size() && i-s.front()+1>size)//当当前窗口移出队首元素所在的位置,即队首元素坐标对应的num不在窗口中,需要弹出
s.pop_front();
s.push_back(i);//把每次滑动的num下标加入队列
if(size&&i+1>=size)//当滑动窗口首地址i大于等于size时才开始写入窗口最大值
res.push_back(num[s.front()]);
}
return res;
}
};

思路分析:

第一种解决方案

常规操作:

1.左指针右指针分别在数组上滑动

2.循环i在滑动过程中(终止条件,right指针超过数组的边界),调用查找最大值的函数,返回每次滑动过程的最大值。

第二种操作方案:

采用双队列(尾端(相当于左端)到初始端,排列顺序从小到大),循环队列里面的值与数组里的比较,数组的大的话,就相当于排序放在双头队列里面(删除比他小的)。保证开头一直是最大的。还需要考虑一个情况,双头队列中的值过期的情况,i-size<peakFirst()的值,就可以删掉这个过期的值了pollFirst()。

输出结果的条件:当i>size-1的时候,相当于宽度已经到达了3了,已经可以开始华东取最大值了

总结:

1.LinkedList 是双头队列,具有安全的查询(peek(peekFirst,last方法))和增加(addLast,addFirst)安全的删除(poolFirst,poolLast方法)


树:

题目一:

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。


思路:

通过左子树和右子树递归找到每个节点。

总结前序遍历是 根节点,左节点,右节点这样的顺序。中序遍历是左节点,根节点,右节点这样的顺序。front节点表示前序遍历的列表。mid节点表示中序遍历的列表。

分析:

1根节点每次都是等于前序节点的第一个节点

2.根据找到的根节点(得到一个count指针),在中序遍历里面能够找到左子树的范围(根节点前面所有的值)left2 到left2+count

3.根据根节点可以在找到右子树的范围(left2+count+1 到 right2的范围)

   遍历条件:

    1.node.left(pre,in,left1+1,left1+count,left2,left2+count-1)

     2.node.right(pre,in,left1+count+1,right1,left2+count+1,right2)

 

边界条件:1.front 和 mid 不为空

                   2.两个列表左节点的需要小于长度

                   3.左节点的长度也要小于右节点的长度。


题目二:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

思路:反正返回的是这个点的中序遍历的下一个节点。所以分为几种情况

1.该节点有右节点

2.该节点无右节点

  •    属于父节点的左节点,直接返回父节点
  • 属于右节点,返回该节点的父节点的父节点(中序排序造成)

边界条件:

1. pNode该节点是初始根节点,直接返回null

2.PNode无右子树的情况下,没有left及right 元素,为空,会报空指针异常(判断方式,如果他父节点的父节点的左节点和Pnode的父节点不是同一个值就说明是这种异常情况,因此返回Null)


题目三

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。


思路:

1.创建变量 num记录奇数层还是偶数层  创建队列,用来存储待处理的种子节点。在每次处理每层的过程中创建零食List装从队列里面取出的值。创建Sum值,初始化为root的个数也就是一。当在处理每层的过程中,创建temp变量,记录下一层的处理的个数。这一层循环处理结束后,将sum的值变为Temp的值。

2.两层循环,第一层循环判断队列是否为空,第二层处理每一层的取到元素加入零时List里面

3.将零时的List(当num为偶数的时候,处理一下倒序,技术的时候不用管)加入最终结果的LIst


临界条件:

1.判断队列是否为空,

2.判断左右节点是否有

3.判断根节点是否为空


题目四:

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。例如,传入的数据为:[5,2,3,4,1,6,7,0,8],那么按照要求,输出是"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "

思路:

1.用两个堆排序(这里用java的封装对象 小顶堆,排序方式是从小到大PriorityQueue,poll的方式是弹出队首元素,因此小顶堆弹的是最大值,大顶堆弹得是最小元素)

2.定义当目前的元素个数为奇数的时候,增加到小顶堆里面;反之增加到大顶堆里面。增加完之后。奇偶性发生改变,用参数sum记录奇偶性

3.判断大顶堆的最小值和小顶堆的最小值比较,如果最大值大于最小值则交换。

3.根据sum奇偶性判断中位数 。

总结:

1.Priority队列,自动默认从大到小排序。该队列因为是队列,所以先进先出,出的时候只能考虑poll()取得队首元素(默认表示最小值)


排序:

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

思路总结:


迭代:

1.跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。


遞歸:很簡單

動態規劃。就是把那個關係式對應的子問題的結果用數組存儲,遍歷(循環)然後返回arr[target]的值


2一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

思路 和上題類似,增加一個sum變量,記錄前面每一步的結果,循環對arr[i]進行賦值,更新sum的值

arr[i]=sum+1

sum=sum+arr[i]的值