[leetcode]03

141.环形链表

给你一个链表的头节点head,判断链表中是否有环.

如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环.为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。如果pos-1,则在该链表中没有环.注意:pos不作为参数进行传递,仅仅是为了标识链表的实际情况。如果链表中存在环,则返回true。否则,返回false

进阶:

你能用O(1)空间解决此题吗?

示例 1:

img

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
- 思路:快慢指针
public boolean hasCycle(ListNode head) {
    boolean flag = false;
    if (head == null || head.next == null) {
        return flag;
    }
    ListNode slow = head;
    ListNode fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (fast != null) {
            if (slow == fast) {
                flag = true;
                break;
            }
        }
    }
    return flag;
}

142.环形链表II

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

为了表示给定链表中的环,我们使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。 如果pos-1,则在该链表中没有环。注意,pos仅仅是用于标识环的情况,并不会作为参数传递到函数中。

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

进阶:

你是否可以使用O(1)空间解决此题?

示例 1:

img

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
public ListNode detectCycle(ListNode head) {
    ListNode fast = head, slow = head;
    while (true) {
        if (fast == null || fast.next == null) return null;
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) break;
    }
    fast = head;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return fast;
}

环形链表的数学证明:
在这里插入图片描述

设链表长度为 n n n,环的长度为 m m m,则 n − m n-m nm个节点从head开始, m m m个节点从相遇点开始,设现在有快慢指针slowfast,初始都指向head,假设他们在某点重合,这个点与相遇点之间一共有 c c c个节点.

接下来证明他们一定可以重合:

slow走了 x x x步,那么fast就走了 2 x 2x 2x步,他们应该满足下面的等式
{ x = c + ( n − m ) − 1 + k 1 m 2 x = c + ( n − m ) − 1 + k 2 m \left\{\begin{aligned} x& = c+(n-m)-1+k_1m\\ 2x & = c+(n-m)-1+k_2m \\ \end{aligned}\right. {x2x=c+(nm)1+k1m=c+(nm)1+k2m
其中 k 1 , k 2 ∈ N k_1,k_2\in\mathbb{N} k1,k2N.
化简上面的式子就可以得到:
{ x = ( k 1 − k 2 ) m n + c − 1 = ( k 2 − 2 k 1 + 1 ) m \left\{\begin{aligned} x&=(k_1-k_2)m\\ n+c-1&=(k_2-2k_1+1)m \end{aligned}\right. {xn+c1=(k1k2)m=(k22k1+1)m
注意到, k 1 , k 2 , c k_1,k_2,c k1,k2,c是未知数,只要他们存在且符合逻辑,就代表两个指针一定可以重合.

首先看第一个等式,当 k 1 , k 2 k_1,k_2 k1,k2确认的时候,由于 m m m是已知数,所以 x x x可以直接确定.

然后看第二个等式,由于 n + c − 1 n+c-1 n+c1一定是大于 n n n的,所以一定大于 m m m.那么这就代表我们可以通过调整 k 1 , k 2 , c k_1,k_2,c k1,k2,c的值让右边等于左边!举一个很简单的例子,令 k 2 = 3 , k 1 = 0 k_2=3,k_1=0 k2=3,k1=0,那么右边就变成了 4 m 4m 4m,这代表快指针已经走了4圈环,而慢指针一圈都没有走完!那么这种情况是合理的吗?答案:

只要能够取到一个合适的 c c c就是合理的!( 0 < c ≤ m 0<c\le m 0<cm).

综上所诉,两个指针是一定能够相遇的!这就解释了第一个问题.

接下来我们来解释第二个问题,首先还是考虑下面这个式子
x = ( k 1 − k 2 ) m x=(k_1-k_2)m x=(k1k2)m
可以注意到什么?如果x再加上 ( n − m ) (n-m) (nm),那么就等于 n + k 3 m n+k_3 m n+k3m!!! k 3 k_3 k3是一个整数,是多少不重要,重要的是:这说明此时此刻slow指针走了一整个链表的长度加上若干圈环的长度!!!

假如说链表是有环的,那么slow指针在走了 n n n步以后,必然会出现在相遇点!!!之后每次再走 m m m步,都会出现在相遇点!!!

而如果此时此刻一点指针从head节点开始出发,那么在它走了 n − m n-m nm步之后,是不是也会出现在相遇点?!

所以两个指针一定会在 n − m n-m nm步之后重合!!!

2.两数相加

给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0开头。

示例 1:

image

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

注意几个if判断的条件,不要随意更改

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    ListNode head = null, tail = null;
    int count  = 0;
    while (l1 != null || l2 != null) {
        int n1 = l1 != null ? l1.val : 0;
        int n2 = l2 != null ? l2.val : 0;
        int sum = n1 + n2 + count ;
        if (head == null) {
            head = tail = new ListNode(sum % 10);
        } else {
            tail.next = new ListNode(sum % 10);
            tail = tail.next;
        }
        count  = sum / 10;
        if (l1 != null) {
            l1 = l1.next;
        }
        if (l2 != null) {
            l2 = l2.next;
        }
    }
    if (count  > 0) {
        tail.next = new ListNode(count);
    }
    return head;

}

19.删除链表的倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

**进阶:**你能尝试使用一趟扫描实现吗?

示例 1:

image

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

public ListNode removeNthFromEnd(ListNode head, int n) {
    if(head.next == null){
        return null;
    }
    ListNode dummy = new ListNode(0,head);
    ListNode slow = dummy;
    ListNode fast = head;
    for(int i = 0;i<n;i++){
        fast = fast.next;
    }
    while(fast!=null){
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;
    return dummy.next;
}

11.盛水最多的容器

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器。

示例 1:

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

public int maxArea(int[] height) {
    int i=0,j=height.length - 1,res=0;
    while(i<j){
        res = height[i]<height[j]?
        Math.max(res,(j-i)*height[i++]):
        Math.max(res,(j-i)*height[j--]);
    }
    return res;
} 

15.三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        for(int k=0;k<nums.length-2;k++){
            if(nums[k] > 0) break;
            if(k>0 && nums[k]==nums[k-1]) continue;
            int i=k+1,j=nums.length-1;
            while(i<j){
                int sum = nums[k]+nums[i]+nums[j];
                if(sum<0){
                    while(i < j && nums[i] == nums[++i]);
                }else if(sum > 0){
                    while(i < j && nums[j] == nums[--j]);
                }else{
                    res.add(new ArrayList<Integer>(Arrays.asList(nums[k],nums[i],nums[j])));
                    while(i < j && nums[i] == nums[++i]);
                    while(i < j && nums[j] == nums[--j]);
                }
            }
        }
        return res;
    }

138.随机链表的复制

给定一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

要求返回这个链表的 深拷贝。

我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。

示例 1:

image

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        for (Node node = head; node != null; node = node.next.next) {
            Node nodeNew = new Node(node.val);
            nodeNew.next = node.next;
            node.next = nodeNew;
        }
        for (Node node = head; node != null; node = node.next.next) {
            Node nodeNew = node.next;
            nodeNew.random = (node.random != null) ? node.random.next : null;
        }
        Node headNew = head.next;
        for (Node node = head; node != null; node = node.next) {
            Node nodeNew = node.next;
            node.next = node.next.next;
            nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null;
        }
        return headNew;
    }

53. 最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组
是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:

输入:nums = [1]
输出:1
示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

  • 思路:用动态规划的思想,

定义状态(定义子问题)

dp[i]:表示以nums[i]结尾连续子数组的最大和.

状态转移方程

根据状态的定义,有dp[i]=dp[i-1]+nums[i]

但是dp[i-1]有可能是负数,此时nums[i]加上前面的dp[i-1]值不会变大,所以dp[i]=nums[i]

否则dp[i-1]为正数,那么dp[i]=dp[i-1]+nums[i]

总结来说那就是:
d p [ i ] = max ⁡ { n u m s [ i ] , d p [ i − 1 ] + n u m s [ i ] } dp[i]=\max\{nums[i],dp[i-1]+nums[i]\} dp[i]=max{nums[i],dp[i1]+nums[i]}
初始值

显然dp[0]=nums[0]

public int maxSubArray(int[] nums) {
    int pre=0;
    int res=nums[0];
    for(int num:nums){
        pre=Math.max(pre+num,num);
        res=Math.max(res,pre);
    }
    return res;
}

56.合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
  • 思路:

首先对原来的数组的第一个数字进行排序,这里排序需要用到比较器,Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0]);的含义就是根据数组第[0]个元素进行降序排序.

排序完成后就是建立答案的集合,如果集合为空,就先将数组第一个元素放进去,如果不为空就进行比较:若当前区间的左边比答案最右边区间的右边还大(例如interval为[3,5],但是res最右边的元素为[1,2]),那么就添加这个区间进入集合

否则说明区间一定有交叉(例如interval为[2,4],res最右边元素为[1,3]),此时就更新res区间,右边元素等于max(3,4),最后返回res即可

public int[][] merge(int[][] intervals) {
    if (intervals.length < 2) {
        return intervals;
    }
    Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0]);
    List<int[]> res = new ArrayList<>();
    for (int[] interval : intervals) {
        if (res.size() == 0 || res.get(res.size() - 1)[1] < interval[0]) {
            res.add(interval);
        } else {
            res.get(res.size() - 1)[1] = Math.max(res.get(res.size() - 1)[1], interval[1]);
        }
    }
    return res.toArray(new int[res.size()][2]);
}

189.轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2:

输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]

方法一:使用额外的数组(时间复杂度为O(n),空间复杂度为O(n))

public void rotate(int[] nums,int k){
	int n = nums.length;
	int[] newArr = new int[n];
	for(int i=0;i<n;i++){
		newArr[(i+k)%n] = nums[i];
	}
	System.arraycopy(newArr,0,nums,0,n);
}

方法二:数组翻转(时间复杂度为O(n),空间复杂度为O(1))

public void reverse(int[] nums,int start,int end){
	while(start<end){
		int temp = start;
		nums[start] = nums[end];
		nums[end] = temp;
		start++;
		end++;
	}
}
public void rotate(int[] nums,int k){
	k = k%nums.length;
	reverse(nums,0,nums.length-1);
	reverse(nums,0,k-1);
	reverse(nums,k,nums.length-1);
}

238.除自身以外数组的乘积

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

请 不要使用除法,且在O(n)时间复杂度内完成此题。空间复杂度可以做到O(1)吗?( 出于对空间复杂度分析的目的,输出数组 不被视为 额外空间。)

  • 思路:

我们不必将所有数字的乘积除以给定索引处的数字得到相应的答案,而是利用索引左侧所有数字的乘积和右侧所有数字的乘积(即前缀与后缀)相乘得到答案。具体的答案我们可以直接在输出数组上面运算.

扫描一次不行,我们扫描两次!

public int[] productExceptSelf(int[] nums){
	int length = nums.length;
	int[] ans = new int[length];
	ans[0] = 1;
	//ans[i]表示索引i左侧所有元素的乘积
	for(int i=1;i<length;i++){
		ans[i]=ans[i-1]*nums[i-1];
	}
	//R为右侧所有元素的乘积
	int R=1;
	for(int i=length-1;i>=0;i--){
		ans[i]=ans[i]*R;
		R = R*nums[i];
	}
	return ans;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值