文章目录
前情提要
刚刷完牛客网的剑指offer,写个博客对自己的一个总结。第一次刷,很多题只会暴力破解,我把做每道题的个人思路和他人思路用java进行了编写,如果在这篇文章找不到可以试试看看别的区域,因为很多的树的题都是用递归进行做的,我把其归在了树里面。希望自己的博客能对大家有帮助,准备下一个开leetcode hot100题的坑了。
删除链表中重复的结点
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
他人思路:
自己的思路把问题想得复杂化了,自己想着用MapSet进行判断,看是否出现过,但是没有考虑到这个链表是有序的,可以直接对相邻的元素进行比较
- 首先添加一个头节点,以方便碰到第一个,第二个节点就相同的情况
2.设置 pre ,last 指针, pre指针指向当前确定不重复的那个节点,而last指针相当于工作指针,一直往后面搜索
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode deleteDuplication(ListNode pHead)
{
ListNode head = new ListNode(0);
head.next = pHead;
ListNode pre = head;
ListNode current = head.next;
while(current!=null){
if(current.next!=null && current.val == current.next.val){
while(current.next!=null && current.val == current.next.val){
current = current.next;
}
pre.next = current.next;
current = current.next;
}else{
pre = current;
current = current.next;
}
}
return head.next;
}
}
链表中倒数第k个结点
题目描述
输入一个链表,输出该链表中倒数第k个结点。
个人思路:
第一次循环找到链表的结点个数。从前向后找倒数即可
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if(head==null){
return null;
}
int size = 0;
ListNode current = head;
while(current!=null){
size++;
current = current.next;
}
if(size<k){
return null;
}
int i = 1;
k = size-k+1;
current = head;
while(i<=size){
if(i==k){
return current;
}
i++;
current = current.next;
}
return current;
}
}
时间复杂度:O(2n) = O(n)
空间复杂度:O(1)
总结:
循环了两次,运行时间较多
他人思路:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if(head == null || k<=0){
return null;
}
//设置两个指针,p2指针先走(k-1)步,然后再一起走,
//当p2为最后一个时,p1就为倒数第k个数
ListNode current1 = head;
ListNode current2 = head;
//p2先走,走k-1步,如果k大于链表长度则返回 空,否则的话继续走
while(k > 1){
if(current1.next==null){
return null;
}else{
current1 = current1.next;
k--;
}
}
//两个指针一起 走,一直到p2为最后一个,p1即为所求
while(current1.next!=null){
current1 = current1.next;
current2 = current2.next;
}
return current2;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
复杂链表的复制
题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
个人思路:
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead==null){
return null;
}
RandomListNode copyHead = new RandomListNode(pHead.label);
RandomListNode current = pHead;
RandomListNode copyCurrent = copyHead;
while(current!=null){
copyCurrent.next = new RandomListNode(current.next.label);
copyCurrent.random = new RandomListNode(current.random.label);
current = current.next;
copyCurrent = copyCurrent.next;
}
return copyHead;
}
}
总结:
currentNode.radom.next就是新的随机节点啊,如果是currentNode.random的话还是原来的链表里面的元素,虽然他们俩label一样…不知道我说的你听懂没
画图你就会清晰一些了,currentNode.random.next你可以拆分成currentNode.random和next,那这个currentNode.random就是原链表currentNode节点的任意指针指向的节点,然后我们在构建新链表的时候,每个新链表节点都是接在原链表节点的后面,那么新链表的对应节点就应该接在原链表的currentNode.random节点后面,也就是next指向的节点了..没法发图,你可以尝试理解一下
他人思路:
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
/*
*解题思路:
*1、遍历链表,复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
*2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
*3、拆分链表,将链表拆分为原链表和复制后的链表
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
if(pHead == null) {
return null;
}
RandomListNode currentNode = pHead;
//1、复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
while(currentNode != null){
RandomListNode cloneNode = new RandomListNode(currentNode.label);
RandomListNode nextNode = currentNode.next;
currentNode.next = cloneNode;
cloneNode.next = nextNode;
currentNode = nextNode;
}
currentNode = pHead;
//2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
while(currentNode != null) {
currentNode.next.random = currentNode.random==null?null:currentNode.random.next;
currentNode = currentNode.next.next;
}
//3、拆分链表,将链表拆分为原链表和复制后的链表
currentNode = pHead;
RandomListNode pCloneHead = pHead.next;
while(currentNode != null) {
RandomListNode cloneNode = currentNode.next;
currentNode.next = cloneNode.next;
cloneNode.next = cloneNode.next==null?null:cloneNode.next.next;
currentNode = currentNode.next;
}
return pCloneHead;
}
}
从头到尾打印链表
题目描述
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
个人思路:
多借助一个list
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<Integer>();
ArrayList<Integer> list2 = new ArrayList<Integer>();
ListNode current = listNode;
while(current!=null){
list.add(current.val);
current = current.next;
}
for(int i=list.size()-1;i>=0;i--){
list2.add(list.get(i));
}
return list2;
}
}
时间复杂度:O(2n) 运行时间:18ms
空间复杂度:O(2n) 占用内存:9412k
总结:
多用了一个list,浪费了空间
个人思路:
不借助第二个list
import java.util.ArrayList;
class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<Integer>();
ListNode current = listNode;
while(current!=null){
list.add(current.val);
current = current.next;
}
for(int i=0;i<(list.size()+1)/2;i++){
int temp = list.get(i);
list.set(i,list.get((list.size()-i-1)));
list.set(list.size()-i-1,temp);
}
return list;
}
}
时间复杂度:O(n+n/2) = O(n) 运行时间:17ms
空间复杂度:O(n) 占用内存:9560k
合并两个排序的链表
题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
个人思路:
将list1链表插入list2中,四种情况讨论
class ListNode {
int val;
ListNode next = null;
ListNode(){
}
ListNode(int val) {
this.val = val;
}
}
public class Solution {
public static void main(String[] args) {
Solution s = new Solution();
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
ListNode node6 = new ListNode(6);
ListNode node7 = new ListNode();
node1.next = node3;
node3.next = node5;
node2.next = node4;
node4.next = node6;
ListNode head = s.Merge(node1, node2);
}
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null){
return list2;
}
if(list2==null){
return list1;
}
ListNode current1 = list1;
ListNode after = list1.next;
ListNode current2 = list2;
ListNode pre = null;
ListNode head = list2;
while(current1!=null){
while(current2!=null){
if(current1.val <= current2.val && pre==null){
pre = current1;
current1.next = current2;
head = pre;
break;
}else if(current1.val <= current2.val && pre!=null){
pre.next = current1;
current1.next = current2;
break;
}else if(current1.val > current2.val && current2.next!=null){
pre = current2;
current2 = current2.next;
}else if(current1.val > current2.val && current2.next==null){
current2.next = current1;
break;
}
}
if(after!=null){
current1 = after;
after = current1.next;
}else{
break;
}
}
return head;
}
}
总结:
只通过了一部分代码{135}{246}着个例子通过了,但是{135}{}这个例子直接报死循环或者循环时间过多,不知道因为什么
他人思路:
递归版本:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null){
return list2;
}
if(list2==null){
return list1;
}
if(list1.val < list2.val){
list1.next = Merge(list1.next,list2);
return list1;
}else{
list2.next = Merge(list1,list2.next);
return list2;
}
}
}
非递归版本:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null){
return list2;
}
if(list2==null){
return list1;
}
ListNode current = null;
ListNode head = null;
while(list1!=null && list2!=null){
if(list1.val < list2.val){
if(head==null){
head = list1;
current = list1;
}else{
current.next = list1;
current = list1;
}
list1 = list1.next;
}else{
if(head==null){
current = list2;
head = list2;
}else{
current.next = list2;
current = list2;
}
list2 = list2.next;
}
}
if(list1==null){
current.next = list2;
}else{
current.next = list1;
}
return head;
}
}
反转链表
题目描述
输入一个链表,反转链表后,输出新链表的表头。
他人想法:
head为当前节点,如果当前节点为空的话,那就什么也不做,直接返回null;
当前节点是head,pre为当前节点的前一节点,next为当前节点的下一节点
需要pre和next的目的是让当前节点从pre->head->next1->next2变成pre<-head
即pre让节点可以反转所指方向,但反转之后如果不用next节点保存next1节点的话,此单链表就此断开了
所以需要用到pre和next两个节点
做循环,如果当前节点不为空的话,始终执行此循环,此循环的目的就是让当前节点从指向next到指向pre
如此就可以做到反转链表的效果
先用next保存head的下一个节点的信息,保证单链表不会因为失去head节点的原next节点而就此断裂
如果head为null的时候,pre就为最后一个节点了,但是链表已经反转完毕,pre就是反转后链表的第一个节点
直接输出pre就是我们想要得到的反转后的链表
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null)
return null;
ListNode pre = null;
ListNode next = null;
while(head!=null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
链表中环的入口
题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
个人思路:
用一个set存储结点,如果出现重复的则返回这个值,如果没出现则返回null
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead){
ListNode current = pHead;
Set<ListNode> set = new HashSet<ListNode>();
while(current!=null){
if(set.contains(current)){
return current;
}
set.add(current);
current = current.next;
}
return null;
}
}
时间复杂度:O(n)
空间复杂度:O(n)
他人思路:
第一步,找环中相汇点。分别用p1,p2指向链表头部,p1每次走一步,p2每次走二步,直到p1p2找到在环中的相汇点。
第二步,找环的入口。接上步,当p1p2时,p2所经过节点数为2x,p1所经过节点数为x,设环中有n个节点,p2比p1多走一圈有2x=n+x; n=x;可以看出p1实际走了一个环的步数,再让p2指向链表头部,p1位置不变,p1,p2每次走一步直到p1==p2; 此时p1指向环的入口。
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead){
if(pHead==null || pHead.next==null){
return null;
}
ListNode p1 = pHead;
ListNode p2 = pHead;
while(p2!=null && p2.next!=null){
p1 = p1.next;
p2 = p2.next.next;
if(p1==p2){
p2 = pHead;
while(p1!=p2){
p1 = p1.next;
p2 = p2.next;
}
if(p1==p2){
return p1;
}
}
}
return null;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
个人思路:
从数组中找到奇数和偶数,存到两个单链表中,然后讲偶数的单链表加到奇数的单链表后
class ArrayNode{
int num;
ArrayNode next = null;
public ArrayNode(){}
public ArrayNode(int num){
this.num = num;
}
}
public class Solution {
public void reOrderArray(int [] array) {
ArrayNode head1 = new ArrayNode();
ArrayNode tail1 = new ArrayNode();
ArrayNode head2 = new ArrayNode();
ArrayNode tail2 = new ArrayNode();
tail1 = head1;
tail2 = head2;
for(int i=0;i<array.length;i++){
if(array[i]%2==1){
ArrayNode node = new ArrayNode(array[i]);
tail1.next = node;
tail1 = node;
}
if(array[i]%2==0){
ArrayNode node = new ArrayNode(array[i]);
tail2.next = node;
tail2 = node;
}
}
tail1.next = head2.next;
ArrayNode current = head1.next;
int i = 0;
while(current != null){
array[i++] = current.num;
current = current.next;
}
}
}
时间复杂度:O(n)
空间复杂度:O(n)
总结:
这是用空间换取时间
这里的链表也可以用数组进行替代
圆圈中最后剩下的数
题目描述
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
个人思路:
用循环单链表实现,每次到该删除的时候就进行删除
class Child{
int index;
Child next = null;
public Child(int index){
this.index = index;
}
}
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n==0){
return -1;
}
Child child0 = new Child(0);
Child current = child0;
for(int i=1;i<n;i++){
Child child = new Child(i);
current.next = child;
current = child;
}
current.next = child0;
int count = -1;
while(current.next != current){
if((count+1) == (m-1)){
current.next = current.next.next;
count = -1;
}else{
current = current.next;
count++;
}
}
return current.index;
}
}
时间复杂度O(m*n)
时间复杂度:O(n)
总结:
总体来说比下面的运行时间要低一些,因为没有用的数都被删掉了用数组实现,会额外判断一下不存在的数字
他人思路:
用数组模拟环实现
public class Solution {
public static int LastRemaining_Solution(int n, int m) {
if(n==0){
return -1;
}
int[] a = new int[n];
for(int i=0;i<n;i++){
a[i] = i;
}
int count = 0;//计算走的步数,用来模拟循环
int time = -1;//计算小朋友查的数字
int i = 0;
while(n > 0){
if(a[i] != -1 && n==1){ //找到了数组中仅剩的那个
break;
}
if(a[i] != -1){
time++;
count++;
}
if(time == m-1){
a[i] = -1;
time = -1;
count--;
n--;
}
if(count == n){
count = 0;
i = -1;
}
i++;
}
return a[i];
}
}
时间复杂度:O(m*n)
空间复杂度:O(n)
总结:
时间复杂度太高了
他人思路:
在这n个数字中,第一个被删除的数字是(m-1)%n
接下来我们把剩下的的这n-1个数字的序列k+1,…,n-1,0,…k-1作一个映射,映射的结果是形成一个从0到n-2的序列:
k+1 -> 0
k+2 -> 1
…
n-1 -> n-k-2
0 -> n-k-1
…
k-1 -> n-2
class Child{
int index;
Child next = null;
public Child(int index){
this.index = index;
}
}
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n==0){
return -1;
}
Child child0 = new Child(0);
Child current = child0;
for(int i=1;i<n;i++){
Child child = new Child(i);
current.next = child;
current = child;
}
current.next = child0;
int count = -1;
while(current.next != current){
int deleteIndex = (m-1)%n;
current = deleteChild(current,deleteIndex,n);
n--;
}
return current.index;
}
public Child deleteChild(Child current,int deleteIndex,int length){
for(int i=0;i<length;i++){
if(i == deleteIndex){
current.next = current.next.next;
break;
}else{
current = current.next;
}
}
return current;
}
}
时间复杂度O(n)
空间复杂度O(n)
总结:
因为还有删除,所以我个人认为不是O(n),但是小于O(n*m)
两个链表的第一个公共节点
题目描述
输入两个链表,找出它们的第一个公共结点。
**个人思路:**因为刚开始做,没有理解到结点是指整个ListNode都是一样得,拥有得是同一个地址,而不是简单得数字一样,所以说找到公共结点,指的是这个结点后面的结点全部相同。
他人思路:
找出2个链表的长度,然后让长的先走两个链表的长度差,然后再一起走
(因为2个链表用公共的尾部)
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
int length1 = findLength(pHead1);
int length2 = findLength(pHead2);
if(length1 > length2){
pHead1 = stepListNode(pHead1,length1-length2);
}else{
pHead2 = stepListNode(pHead2,length2-length1);
}
while(pHead1 != null){
if(pHead1 == pHead2){
return pHead1;
}
pHead1 = pHead1.next;
pHead2 = pHead2.next;
}
return null;
}
public int findLength(ListNode pHead){
if(pHead == null){
return 0;
}
int sum = 1;
while(pHead.next != null){
pHead = pHead.next;
sum++;
}
return sum;
}
public ListNode stepListNode(ListNode pHead,int step){
for(int i=0;i<step;i++){
pHead = pHead.next;
}
return pHead;
}
}
时间复杂度:O(n)
空间复杂度:O(n)
他人思路:
长度相同有公共结点,第一次就遍历到;没有公共结点,走到尾部NULL相遇,返回NULL
长度不同有公共结点,第一遍差值就出来了,第二遍一起到公共结点;没有公共,一起到结尾NULL。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1 != p2){
p1 = (p1==null ? pHead2 : p1.next);
p2 = (p2==null ? pHead1 : p2.next);
}
return p1;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
总结:
如果两个链表的长度相同并且没有相同的结点,这是死循环?这个说法是错误得;因为是相同长度,走到最后,相同得一定还是null