一、两两交换链表中的节点
问题:循环的条件应如何写。前提:在交换节点的时候,必须要借助前一个结点
节点:偶数个:当cur.next==null,说明没有节点了。
奇数个: 当cur.next.next==null,遍历结束。
对循环条件的解释:每次交换两个节点,12 34 56 78 所以借助的前一个结点一定是偶数。因此如果是偶数个,那么最后一个偶数节点的cur.next一定是null,如果是奇数个,最后一个偶数节点的cur.next.next一定是null;
代码:
while(cur.next!=null&&cur.next.next!=null){
//开始交换,交换的时候 需要进行三次交换 需要借助临时结点存储
ListNode temp=cur.next;(1)ListNode temp2=cur.next.next;(3)
cur.next=cur.next.next;cur.next=temp;temp.next=temp2;
//移动cur结点 cur=cur.next.next;
最后 return virtual.next
}
二、删除链表中的倒数第N个结点(常规做法/双指针)
常规做法:
首先求出链表的长度length.然后算出倒数第N个结点的前一个结点。就是length-n;
然后移动到前一个结点,就可以。如何移动:
int m=cnt-n;
for(int i=0;i<m;i++){
cur2=cur2.next;
}
双指针法
核心:要删除倒数第n个结点,快指针就要比满指针多走(n+1)步,然后快慢指针同时前进,这样快指针走到末尾,慢指针就走到了要删除结点的前一个结点。一共有x个结点,慢指针要走的共有x-n-1个。
步骤1.先让快指针向前移动(n+1)个
步骤2.然后快慢指针同时移动至fast==null
步骤3.删除结点
public ListNode removeNthFromEnd(ListNode head, int n) {
//快慢指针法
ListNode virtual=new ListNode();
virtual.next=head;
ListNode fast=virtual;
ListNode slow=virtual;
//将fast向后移动(n+1)个
for(int i=1;i<=n+1;i++){
fast=fast.next;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
//slow移动到要删除结点的前一个结点
slow.next=slow.next.next;
return virtual.next;
}
三、环形链表
如何判断有环:
用快慢指针判断是否有环,快指针每次都走2个,慢指针每次走1个。
如果有环的话,那么快慢指针就一定会相遇的。(慢指针进入环之后,快指针就会去追它)
如何找到入口
根据数学公式推导:(从卡尔哥那里学到的)
2(x+y)=n(y+z)+x+y;
x=(n-1)(y+z)+z;
也就是说从head到入口的距离是等于(n-1)圈循环和从相遇的点到入口点的距离。
public ListNode detectCycle(ListNode head) {
//首先判断有环 如果能相遇 就说明有环
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null&&fast.next.next!=null){
fast=fast.next.next;//快指针移动
slow=slow.next;//慢指针移动
if(slow==fast){
//相等 代表相遇了
ListNode index1=slow;
ListNode index2=head;
//找到入口点
while(index1!=index2){
index1=index1.next;
index2=index2.next;
}
return index1;
}
}
return null;
}
四、有效的字母异位词:(哈希表数组)
使用哈希表;出现的英文字母在数组中++,然后再--,如果最后遍历数组中出现不等于0的,就说明不是有效的字母异位词。
public boolean isAnagram(String s, String t) {
if(s.equals(t))return false;
int nums[] = new int[26];
for (int i = 0; i < s.length(); i++) {
nums[s.charAt(i)-97]++;
}
for (int i = 0; i < t.length(); i++) {
nums[t.charAt(i)-97]--;
}
for(int i:nums){
if(i!=0)return false;
}
return true;
}
五、两个数组的交集(哈希表set)
将第一个数组arr1的每一个元素都添加到set集合中,然后遍历第二个数组arr2,看set集合中是否存在这些值,如果存在就是交集放到result(set)中。最后转化为数组,返回。
六、两数之和(map哈希表)
给定一个数组和一个target值,要求从数组中找到两个数,相加之和为target。然后返回下标
思路:
1.建立map集合用来存放遍历过的元素,key为数组值,value为数组的下标。
2.用一个for循环遍历nums中的每一个元素,判断map中是否存在target-nums[i],
3.如果存在直接返回map.get(target-nums[i]),如果不存在就往map中添加键值对
4.如果遍历结束都没找到,那么直接返回null
代码:
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap();// 数组值为key 下标为value
for (int i = 0; i < nums.length; i++) {
int result = target - nums[i];
boolean flag = map.containsKey(result);
if(flag){
return new int[]{map.get(result),i};
}else{
map.put(nums[i],i);
}
}
return null;
}
七、四数相加:
从四个数组中每次选择一个数,要求相加之和为0,求有多少种加法。
和两数之和有一点共同之处,都用到了map集合去寻找集合中是否存在x-map.getKey();
思路:首先将数组两两相加,将相加的和和出现的次数放到map中存储,然后解题的关键就变成了:寻找0-map.getKey()这个key值在另一个map集合中是否存在以及存在多少次。
找到key值后,应该是两边的value值相乘,而不是相加!!! key(数组值之和)value(出现的次数)
核心代码:
int way = 0;
int index = 1;
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
for (Map.Entry<Integer, Integer> entry : entries) {
int result = 0 - entry.getKey();// 在map1中找key为result的
boolean flag = map1.containsKey(result);
if (flag) {
System.out.println("第" + (index++) + "次,way:" + way);
way = way + entry.getValue() * map1.get(result);
}
}
return way;
八、三数之和(双指针)
整体思路:一个for循环从0开始,i+1为left指针,nums.length-1为right指针。
在left->right这个区域中,搞成升序排列(因为题目中要求返回的是相加为0的值,不要求返回下标,因此升序更方便我们去操作)。如果nums[i]+nums[left]+nums[right]是>0的,那么right就要往左移动,right--;如果是<0的,那么left就要往右移动。left++;如果相等,那么就返回结果。
难点:如何去重?
两方面:先对a去重:当for循环遍历的时候,nums[i]和nums[i-1]相等的话,那么就直接跳过。因为这种情况我们已经在arr[i-1]的时候,考虑过了。
另一方面:对b和c去重:在收割结果之后,如果我们发现nums[left+1]或者nums[right-1]是相等的话。那么就直接跳过,因为这种条件也已经考虑过了。
代码:
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);//对nums进行排序
List<List<Integer>> results=new ArrayList();
int left;
int right;
for(int i=0;i<nums.length;i++){
if(nums[i]>0)return results;
if(i>0&&nums[i]==nums[i-1])continue;
left=i+1;
right=nums.length-1;
while(left<right){
if(nums[i]+nums[left]+nums[right]>0)right--;
else if(nums[i]+nums[left]+nums[right]<0)left++;
else{
//该种情况是等于0 可以收割结果了
List result=new ArrayList();
Collections.addAll(result,nums[i],nums[left],nums[right]);
results.add(result);
//收割完之后 进行去重判断
while(right>left&&nums[left]==nums[left+1])left++;
while(right>left&&nums[right]==nums[right-1])right--;
left++;right--;
}
}
}
return results;
}
九、四数之和(和三数之和相类似 去重和剪枝好复杂(靠))
四数之和是一个双循环和双指针。在进行剪枝,两层都要考虑到。去重的时候内层去重就可以了(内外层的left和right是一样的 因此在内层去重就行了)
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
//外层剪枝
if (i > 0 && nums[i - 1] == nums[i]) {
continue;
}
for (int j = i + 1; j < nums.length; j++) {
//内层剪枝
if (j > i + 1 && nums[j - 1] == nums[j]) {
continue;
}
int left = j + 1;
int right = nums.length - 1;
while (right > left) {
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
left++;
right--;
}
}
}
}
return result;
}
十、反转字符串II(小难)
规则:给定一个字符串 s
和一个整数 k
,从字符串开头算起,每计数至 2k
个字符,就反转这 2k
字符中的前 k
个字符。
如果剩余字符少于 k
个,则将剩余字符全部反转。
如果剩余字符小于 2k
但大于或等于 k
个,则反转前 k
个字符,其余字符保持原样
反转的时候需要比较剩余的字符。
1.如果剩余的字符的长度是k->2k或者>2k,那么就可以将i->i+k-1中的字符串都反转;
2.相反,如果剩余的字符串不够k,那么就将i->s.length()-1的字符串反转。
public String reverseStr(String s, int k) {
char[] ch=s.toCharArray();
for(int i=0;i<ch.length;i+=2*k){
//看范围,看反转k还是剩余的
int start=i;
//如果剩余的足够i+k-1 那么length-1就会>i+k-1 如果不够的话,那就是反转剩余的字符串
int end=Math.min(i+k-1,ch.length-1);
while(start<end){
char temp=ch[start];
ch[start]=ch[end];
ch[end]=temp;
start++;
end--;
}
}
return new String(ch);
}