一、242. 有效的字母异位词
1.1题目
给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的字母异位词。
注意:若 s
和 t
中每个字符出现的次数都相同,则称 s
和 t
互为字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram" 输出: true
示例 2:
输入: s = "rat", t = "car" 输出: false
1.2思路分析
方法1:传统方法
将字符串转换为字符数组,然后分别对两个数组进行排序,然后在调用equals方法进行比较;
方法2:哈希表
因为题目的需求是找"是否存在"的问题,因此可以考虑使用哈希表,对两个字符串中的字符作为数组的下表,其出现的频次作为数组的值,然后根据出现的频次就可以判断两个字符串是否相等。
1.3代码实现
方法1暴力求解
class Solution {
public boolean isAnagram(String s, String t) {
// 注意字符串数组在获取数组长度时调用length()
int length1 = s.length();
int length2 = t.length();
if(length1 != length2){
return false;
}
// 调用toCharArray()方法将字符串转换为字符数组
char[] s1 = s.toCharArray();
char[] s2 = t.toCharArray();
// 调用Array.sort()方法对数组进行排序
Arrays.sort(s1);
Arrays.sort(s2);
// 使用Arrays.equals()方法对数组中的值进行比较
return Arrays.equals(s1,s2);
}
}
方法2哈希表
class Solution {
public boolean isAnagram(String s, String t) {
// 使用哈希表对字符串中的每个字符出现的频次进行统计
// 定义一个新数组
if (s.length() != t.length()) {
return false;
}
// 因为result中每个值存的是对应的字符出现的频次因此,其长度定义为26
int[] result = new int[26];
// 对字符串中的字符出现的频次进行统计
for (int i = 0; i < s.length(); i++) {
result[s.charAt(i) - 'a']++;
}
for (int i = 0; i < s.length(); i++) {
result[t.charAt(i) - 'a']--;
}
for (int i = 0; i < 26; i++) {
if (result[i] != 0) {
return false;
}
}
return true;
}
}
二、349. 两个数组的交集
2.1题目
给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出:[9,4] 解释:[4,9] 也是可通过的
2.2思路分析
该题也可以转化为是否存在问题,即当两个数组中存在相同值时,此时则可以理解为两个数组存在交集;但是输出中要求不能出现冗余,因此考虑使用HashSet。
2.3代码实现
首先判断这两个数组中是否有值,若没有值,直接返回一个空数组,然后定义两个集合,一个用于存放待判断的数组,另一个存放最后的结果,最后再将集合转化为数组返回。
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
// 判断nums1与nums2中是否有值
if(nums1.length == 0 ||nums2.length== 0 || nums2 ==null ||nums1 == null){
return new int[0];
}
// 因为数组中有重复的值,因此我们使用 HashSet对数组去重
HashSet <Integer> set1 = new HashSet<>();
// 定义一个结果hashset
HashSet<Integer> result = new HashSet<>();
for(int i:nums1){
// 将nums1中的数值添加到set1中并去重
set1.add(i);
}
for(int i:nums2){
if(set1.contains(i)){
result.add(i);
}
}
// int [] arr = new int[result.]
return result.stream().mapToInt(x->x).toArray();
}
}
三、202. 快乐数
3.1题目
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
输入:n = 19 输出:true 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1
示例 2:
输入:n = 2 输出:false
3.2思路分析
该题的难点在于如何判断是否进入死循环,那么什么时候迭代过程会进入死循环呢,即当当前的数值在以往的迭代过程中出现过,因此该题同样可以考虑使用HashSet。
3.3代码实现
首先定义一个求和的函数;然后再根据条件迭代即可,注意:(n % 10)*(n % 10)取模的平方时需要对其带括号。
class Solution {
public boolean isHappy(int n) {
// 先判断该数是不是1,若是1则直接返回
if(n == 1){
return true;
}
// 需要判断该数是否为无限循环即存在过问题,因此可以定义一个哈希表
// 返回值是无须的因此可以使用unordered_set<int> set(c++) 或直接使用set
HashSet<Integer> set1 =new HashSet <>();
//循环的条件为 判断值是否存在于hashset中且 n !=1
while(n != 1 && !set1.contains(n)){
set1.add(n);
n = addsum(n);
}
return n == 1;
}
// 对n的各个位置进行平方求和
public int addsum(int n){
int temp = 0;
while(n > 0){
// int res = n % 10;
temp += (n % 10)*(n % 10);
n = n/10;
}
return temp;
}
}
四、1. 两数之和
4.1题目
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6 输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6 输出:[0,1]
4.2思路分析
方法1:暴力求解
使用两次for循环来遍历求解
方法2:哈希表
我们可以将题目转化为寻找target-nums[1]的值是否在nums中存在的问题,又因为题目最后的返回值需要满足条件的下标,因此可以考虑使用HashMap,我们将数组中的值作为HashMap中的key,值的下标作为HashMap中的value。
4.3代码实现
方法1暴力求解
class Solution {
public int[] twoSum(int[] nums, int target) {
int temp = 0;
// int [] k = new int[2] ;
for(int i = 0; i<nums.length-1; i++){
temp = nums[i];
for(int j = i+1 ; j<=nums.length-1 ; j++){
if((temp+nums[j])==target){
// k[0] = i;
// k[1] = j;
return new int[]{i,j};
}
}
}
return new int[0] ;
}
}
方法2哈希表
class Solution {
public int[] twoSum(int[] nums, int target) {
// 因为hashmap是键值对所以其泛型的返回值由两个,并且俩个的返回类型都是Integer
HashMap<Integer, Integer> HashMap = new HashMap<>();
// 定义一个新数组用于存放返回的结果、
int [] result = new int[2];
// 该题属于值是否存在问题即判断目标值与数组值之差是否在数组中,若在将该值添加到
// HashMap中,其中值的大小为key,其下表为vale
// 定义一个变量
for(int i = 0 ; i < nums.length; i++){
if(HashMap.containsKey((target - nums[i]))){
result[0] = i;
// 使用get方法获得 key对应的value
result[1] = HashMap.get((target - nums[i]));
// 如果找到了就退出
break;
}
// 如果没有找到就将该数使用put将其放置到HashMap中
// nums[i]表示key,i表示其具体的值
HashMap.put(nums[i],i);
}
return result;
}
}
五、24. 两两交换链表中的节点
5.1题目
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4] 输出:[2,1,4,3]
示例 2:
输入:head = [] 输出:[]
示例 3:
输入:head = [1] 输出:[1]
5.2思路分析
定义变量对链表中的节点进行交换,该题的难点在于理清楚链表的下一个节点的具体指向。
交换后:
5.3代码实现
/**
* 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 swapPairs(ListNode head) {
ListNode dummpy = new ListNode (-1, head);
ListNode cur = dummpy;
// 定义一个中间节点用于链表交换
ListNode temp1;
ListNode temp2;
ListNode temp3;//用于存放交换完后连接的下一个节点
while(cur.next != null && cur.next.next != null){
temp1 = cur.next;
temp2 = cur.next.next;
temp3 = cur.next.next.next;
cur.next = temp2;
temp2.next = temp1;
temp1.next = temp3;
cur = cur.next.next;
}
return dummpy.next;
}
}
六、19. 删除链表的倒数第 N 个结点
6.1题目
给你一个链表,删除链表的倒数第 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]
6.2思路分析
定义快慢指针,让快指针先走n+1步,慢指针再走,这样就可以保证慢指针最后停留到待删除指针的前一个指针。
6.3代码实现
可以定义虚拟头指针来简化判断过程
/**
* 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 removeNthFromEnd(ListNode head, int n) {
ListNode dummpy = new ListNode(-1,head);
// 定义虚拟头节点来处理删除头节点的情况
// 定义快慢指针求解
ListNode fast = dummpy;
ListNode slow = dummpy;
// 快指针走n+1步
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 dummpy.next;
}
}
七、160. 相交链表
7.1题目
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal
- 相交的起始节点的值。如果不存在相交节点,这一值为0
listA
- 第一个链表listB
- 第二个链表skipA
- 在listA
中(从头节点开始)跳到交叉节点的节点数skipB
- 在listB
中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA
和 headB
传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3 输出:Intersected at '8' 解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。 在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 — 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
示例 2:
输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 输出:Intersected at '2' 解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。 在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 输出:null 解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。 由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。 这两个链表不相交,因此返回 null 。
7.2思路分析
该题需注意当两个链表相交时需满足两个指针完全相同,而不是值相同。
7.3代码实现
首先求出两个链表各自的长度,然后求出两个链表长度的差值,并将较长链表的指针移动到与较短链表的指针末尾对其位置。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// 第一步求链表A和链表B的长度
ListNode cur1 = headA;
ListNode cur2 = headB;
int lenA = 0;
int LenB = 0;
int gap = 0;
while(cur1 != null){
cur1 = cur1.next;
lenA ++;
}
while(cur2 !=null){
cur2 = cur2.next;
LenB++;
}
cur1 = headA;
cur2 = headB;
ListNode temp;
int tempLen = 0;
gap = lenA > LenB ? (lenA-LenB):(LenB-lenA);
// int tempgap = gap;
// 确保A为较长链表
if(LenB > lenA){
temp = cur2;
cur2 = cur1;
cur1 = temp;
tempLen = LenB;
LenB = lenA;
lenA = tempLen;
}
while(gap>0){
cur1 = cur1.next;
gap--;
}
while(cur1!=null && cur2 != null){
if(cur1 == cur2){
return cur1;
}
cur1 = cur1.next;
cur2 = cur2.next;
}
return null;
}
}
八、142. 环形链表 II
8.1题目
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。
8.2思路分析
第一步:判断链表中是否有环
定义快慢指针,让快指针以比慢指针快2倍的速度移动,若快指针可以和慢指针相遇则证明该链表有环。
第二步:找到初始相遇的节点。
假设头节点到初始相遇节点的长度为x,环入口节点到相遇节点的长度为y,相遇节点到换入口的长度为z。
则我们根据快慢指针的移动速度可以列入下表达式:;
进一步化简得;
而,我们取个极限情况,即n=1时,此时。
因此我们可以进一步定义两个指针,一个指针由头指针开始向后运动,另一个指针由相遇位置开始移动,当两个新定义得指针再次相遇时,此时得相遇点则为快慢指针得初始相遇点。
8.3代码实现
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
// 使用快慢指针,快指针以2倍速的方式取去追赶慢指针,当快指针等于慢指针时
// 说明此时有环
ListNode fast = head;
ListNode slow = head;
// 因为快指针一次走两步因此需要判断fast.next是否为空,若不为空则继续循环
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
ListNode index1 = head;
ListNode index2 = fast;
while(index1 != index2){
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}