Loading

2021-08-22 力扣刷题笔记

TOC

每日一题 789. 逃脱阻碍者

题目描述

你在进行一个简化版的吃豆人游戏。你从 [0, 0] 点开始出发,你的目的地是 target = [xtarget, ytarget] 。地图上有一些阻碍者,以数组 ghosts 给出,第 i 个阻碍者从 ghosts[i] = [xi, yi] 出发。所有输入均为 整数坐标 。

每一回合,你和阻碍者们可以同时向东,西,南,北四个方向移动,每次可以移动到距离原位置 1 个单位 的新位置。当然,也可以选择 不动 。所有动作 同时 发生。

如果你可以在任何阻碍者抓住你 之前 到达目的地(阻碍者可以采取任意行动方式),则被视为逃脱成功。如果你和阻碍者同时到达了一个位置(包括目的地)都不算是逃脱成功。

只有在你有可能成功逃脱时,输出 true ;否则,输出 false 。

示例 1:

输入:ghosts = [[1,0],[0,3]], target = [0,1]
输出:true
解释:你可以直接一步到达目的地 (0,1) ,在 (1, 0) 或者 (0, 3) 位置的阻碍者都不可能抓住你。 

示例 2:

输入:ghosts = [[1,0]], target = [2,0]
输出:false
解释:你需要走到位于 (2, 0) 的目的地,但是在 (1, 0) 的阻碍者位于你和目的地之间。

示例 3:

输入:ghosts = [[2,0]], target = [1,0]
输出:false
解释:阻碍者可以和你同时达到目的地。 

示例 4:

输入:ghosts = [[5,0],[-10,-2],[0,-5],[-2,-2],[-7,1]], target = [7,7]
输出:false

示例 5:

输入:ghosts = [[-1,0],[0,1],[-1,0],[0,1],[-1,0]], target = [0,0]
输出:true
解题思路

这个问题看上去很复杂, 其实我们可以让所有的阻碍者去终点堵TMD吃豆人,如果吃豆人比阻碍者先到终点,那吃豆人就逃脱了,反之则没有逃脱。

因为吃豆人和阻碍者每次只能往上下左右行进一步,所以使用曼哈顿距离判断谁先到达终点,曼哈顿距离越短越快到达终点。

代码实现

class Solution {
    public boolean escapeGhosts(int[][] ghosts, int[] target) {
        // 吃豆人距离终点的距离
        int cur = dist(0, 0, target[0], target[1]);
        // 判断每个阻碍者距离终点的位置
        for(int[] gs : ghosts){
            if(cur > dist(gs[0], gs[1], target[0], target[1])) return true;
        }
        return false;
    }

// 计算距离
int dist(int x1, int y1, int x2, int y2) {
return Math.abs(x2 - x1) + Math.abs(y2 - y1);
}

}

一、876. 链表的中间结点

题目描述

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

输入:[1,2,3,4,5]

输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

示例 2:

输入:[1,2,3,4,5,6]

输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。

解题思路

使用快慢指针来解决,快指针和慢指针起始位置相同,快指针每次走两步慢指针每次走一步,当快指针走到链表的最后一个结点时,慢指针刚好走到中间的节点,返回慢指针即可

代码实现

/**
* Definition for singly-linked list.
* public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/

class Solution {
public ListNode middleNode(ListNode head) {
ListNode fast = head;
ListNode slow = head;
// 需要fast和fast的下一个节点都不为null才能继续
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
}

二、19. 删除链表的倒数第 N 个结点

题目描述

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

示例 1:

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

示例 2:

输入:head = [1], n = 1
输出:[]

示例 3:

输入:head = [1,2], n = 1
输出:[1]
解题思路

使用两个指针,一个指针提前走n步,然后两个指针一起走,当前边那个指针指向最后一个节点时,后边的指针就指向了倒数第n个

代码实现

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode pre = new ListNode(0);
        pre.next = head;
        ListNode fast = pre;
        ListNode slow = pre;
        while(n != 0){
            fast = fast.next;
            n--;
        }
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return pre.next;
    }
}

三、3. 无重复字符的最长子串

题目描述

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

示例 4:

输入: s = ""
输出: 0
解题思路

使用一个滑动窗口,窗口中始终存放的是当前最长的无重复字符字串,如例中的pwwkew, 最长的无重复字符的子串为wkekew,长度都为3

那么如何实现呢,定义左右指针left和right指向头部,使用一个HashMap来当作滑动窗口,

  1. 右指针往右遍历字符串,当右指针指向的字符不在map中,则把指向的字符加入到map中,此时最长不重复子串的长度为right - left + 1
  2. 当右指针指向的字符在map中时有两种情况
    1. 如果当前字符包含在有效的子段中,则将left指向重复字符的下一个位置,即在前边的重复的那个字符的下一个位置,如字符串abca,当right遍历到最后那个a时,left就应该指向b,即把第一个a踢出子串,此时的最长子串为bca
    2. 如果当前字符不包含在当前最长的有效字段中,则不能像上边情况一样处理
  3. 为了解决这种情况,我们更新left使left不往回走,left = Math.max(left, map.get(ch) + 1),然后将map中重复字符的索引位置更新为当前指向的索引

代码实现

class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s.length() == 0) return 0;
        int left = 0;
        int max = 0;
        HashMap<Character, Integer> map = new HashMap<>();
        for(int rigth = 0; right < s.length(); rigth++) {
            if(map.containsKey(s.charAt(rigth))) {
                left = Math.max(left, map.get(s.charAt(right)) + 1);
            }
            map.put(s.charAt(rigth), rigth);
            max = Math.max(max, rigth - left + 1);
        }
        return max;
    }
}

四、567. 字符串的排列

题目描述

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。

换句话说,s1 的排列之一是 s2 的 子串 。

示例 1:

输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").

示例 2:

输入:s1= "ab" s2 = "eidboaoo"
输出:false
解题思路

使用一个滑动窗口,窗口大小固定为s1的大小,定义两个字典分别用来存放s1中的字母和s2中的字母,当两个字典相等时,s1即为s2的一个子串的排列

s1的字典是固定的,s2的字典实时变动,由滑动窗口框住的字符确定;左边的出去右边的进来。

字典通过数组实现,使用自身ascii码与小写a的ascii码的相对位置作为下标,每存在一个就加一。

代码实现

class Solution {
    public boolean checkInclusion(String s1, String s2) {
           char[] cs1 = s1.toCharArray();
        char[] cs2 = s2.toCharArray();

if(cs1.length > cs2.length) return false;

int[] dict1 = new int[26];
int[] dict2 = new int[26];

int left = 0;
int rigth = cs1.length - 1;

for(int i = 0; i < cs1.length; i++){
// 初始化s1的字典
dict1[cs1[i] - 'a']++;
}
for(int i = 0; i < rigth; i++){
// 初始化s2上滑动窗口的字典,rigth所在的位置没有放入字典内
dict2[cs2[i] - 'a']++;
}

while(rigth < cs2.length){
// 将rigth所在位置的字符放入字典中
dict2[cs2[rigth] - 'a']++;
if(Arrays.equals(dict1, dict2)) return true;
else{
dict1[cs1[left] - 'a']--;
if(dict1[cs1[left] - 'a'] < 0)
dict1[cs1[left] - 'a'] = 0;
right++;
left++;
}
}
return false;
}
}

posted @ 2021-08-26 00:01  littleziao  阅读(90)  评论(0)    收藏  举报