题目部分来源:
GitHub链接: https://github.com/youngyangyang04/leetcode-master
序号 | 类型 | 题数 |
---|---|---|
1 | 数组 | 9 |
2 | 链表 | 6 |
3 | 哈希表 | 1 |
4 | 字符串 | 2 |
5 | 二叉树 | 8 |
6 | 回溯算法 | 13 |
7 | 贪心 | 2 |
8 | 动态规划 | 3 |
9 | 图论 | 3 |
10 | 并查集 | 2 |
11 | 模拟 | 2 |
12 | 位运算 | 1 |
https://leetcode.cn/problems/how-many-numbers-are-smaller-than-the-current-number/
自写代码思路:
定义一个新数组num,然后在两个循环去遍历nums数组,将每一个key比较结果添加到新数组,返回新数组;
class Solution {
public int[] smallerNumbersThanCurrent(int[] nums) {
int l = nums.length;
int[] num = new int[l];
for(int i = 0; i<l; i++){
int count=0;
for (int j=0;j<l;j++){
if(nums[i]>nums[j] ){
count++;
}
}
num[i] = count;
}
return num;
}
}
改进思路:
采用Map记录key和其相对应的比较结果:
1.将原nums数组复制到res数组并进行排序;
2.排序完毕后 每个key对应的下标就是比之少的个数(相同的看第一个key的下标),将排序结果添加到map中的键,排序结果的下标添加到值;
4.遍历值 再将值添加到res数组中,输出res;
public static void main(String[] args) {
int[] nums = {8,1,2,2,3};
System.out.println(Arrays.toString(nums));
System.out.println(Arrays.toString(smallerNumbersThanCurrent(nums)));
}
public static int[] smallerNumbersThanCurrent(int[] nums){
Map<Integer,Integer> m = new HashMap<Integer,Integer>();
int[] res = Arrays.copyOf(nums,nums.length);
Arrays.sort(res);
for(int i=0;i<nums.length;i++){
if(!m.containsKey(res[i])){
m.put(res[i],i);
}
}
for (int i=0;i<nums.length;i++){
res[i] = m.get(nums[i]);
}
return res;
}
通过已知可以知道满足山脉数组的条件是:
1.数组中元素个数大于2
2.不能有连续的数字
3.有且仅有一个最大的数 其左右两边的数都比它小
自写代码思路:
1.先找出最大的数及其下标
2.采用前后双指针分别对其左右进行遍历看是否满足条件
class Solution {
public boolean validMountainArray(int[] arr) {
if (arr.length<3){
return false;
}
int max = 0;
int index = 0;
for (int i=0;i<arr.length;i++){
if (arr[i]>max){
max = arr[i];
index = i;
}
}
if(index==0 ||index==arr.length-1){
return false;
}
int i = index, j = index;
while (i>0||j<arr.length-1){
if(i>0){
int ii=i-1;
if(arr[ii]>=arr[i]){
return false;
}
}
i--;
if(j<arr.length-1){
int jj=j+1;
if (arr[jj]>=arr[j]){
return false;
}
}
j++;
}
return true;
}
}
左右找山峰版:
使用两种指针,一个从左边找最高山峰,一个从右边找最高山峰,最后判断找到的是不是同一个山峰
public boolean validMountainArray(int[] A) {
int len = A.length;
int left = 0;
int right = len - 1;
while (left + 1 < len && A[left] < A[left + 1])
left++;
while (right > 0 && A[right - 1] > A[right])
right--;
return left > 0 && right < len - 1 && left == right;
}
https://leetcode.cn/problems/unique-number-of-occurrences/
需求分析:
判断数组中的每个元素的出现次数:
要求 不能有相同的出现次数
自写代码分析:
采用map 统计不同数字出现的次数;
将结果统计到一个新数组value中(map中的值)
在用Set过滤掉value中的重复数组;
返回 比较过滤后的数组和value数组中个数结果;
class Solution {
public boolean uniqueOccurrences(int[] arr) {
Map<Integer,Integer> m = new HashMap<Integer,Integer>();
for (int i:arr){
if(!m.containsKey(i)){
m.put(i,1);
}else {
m.put(i, m.get(i)+1);
}
}
ArrayList valus = new ArrayList(m.values());
HashSet<Integer> res = new HashSet<>(valus);
return res.size()==valus.size();
}
}
https://leetcode.cn/problems/move-zeroes/
需求分析:
1.将数组中的所有0元素移动到数组末尾;
2.移动后保持数组中其他元素的相对位置不变;
3. 不使用辅助数组;
自写代码分析:
定义两个指针(search:用来寻找0元素;last:指向数组最后的非零元素)
1.search 从后往前遍历 寻找0元素;
2.当遍历到0时 ,在遍历位置从前往后(last指针处)移动0位置后的所有元素;
3.移动完成后,last位置填补0,last向前移动;
public void moveZeroes(int[] nums) {
int last = nums.length-1;
for (int search=nums.length-1;search>=0;search--){
if(nums[search]==0){
for(int j=search;j<last;j++){
nums[j] = nums[j+1];
}
nums[last] =0;
last--;
}
}
System.out.println(Arrays.toString(nums));
}
邻位交换法
这种方法 不用每次移动好几个数 ,一次只需要移动一个元素
定义两个指针:left、right,一个交换函数swap;
1.right从前往后遍历 ,找到0元素时 就与left交换;
2.left交换完毕之后向后进位;
int left=0;
for(int right = 0;right<nums.length;right++){
if(nums[right] != 0){
swap(nums,right,left);
left++;
}
}
System.out.println(Arrays.toString(nums));
}
private static void swap(int[] nums, int right, int left) {
int flag = 0;
flag = nums[right];
nums[right] = nums[left];
nums[left] = flag;
}
https://leetcode.cn/problems/rotate-array/
需求分析:
“给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。”
我感觉这个需求就类似与给你一个循环的队列,然后你右轮转k个位置,就相当于依此从队尾取出k个元素然后再依次插入到队头;
自写代码分析:
如果采用队列的思想,取一个放一个,每次从后边拿一个放到最前边,那么k次就得挪k次前边数组;
不出意外, 时间超限。
public void rotate(int[] nums, int k) {
for(int i=nums.length-1;i>=nums.length-k;i--){
int flag = nums[nums.length-1];
for (int j=nums.length-1;j>0;j--){
nums[j]=nums[j-1];
}
nums[0]=flag;
}
System.out.println(Arrays.toString(nums));
}
我能想到的第二个方法就是再创建一个新数组,依次将倒数k位的元素和剩余的元素插入新数组
class Solution {
public void rotate(int[] nums, int k) {
ArrayList<Integer> res = new ArrayList<Integer>();
for(int i = nums.length-k;i<nums.length;i++){
res.add(nums[i]);
}
for (int j = 0;j<nums.length-k;j++){
res.add(nums[j]);
}
System.out.println(res);
}
}
改进:通过取余直接一次循环
class Solution {
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);
}
}
方法3:三次反转
lass Solution {
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums,0,nums.length-1);
reverse(nums,0,k-1);
reverse(nums,k,nums.length-1);
System.out.println(Arrays.toString(nums));
}
public static void reverse(int[] nums,int start,int end){
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start += 1;
end -= 1;
}
}
}
https://leetcode.cn/problems/find-pivot-index/
需求分析:
根据题意,数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和;
判断中心坐标我们要从左往右依次判定:
优先要考虑第一个元素之后所有的和是否为零,其次是中间元素,最后考虑除最后一个元素所有元素和;
自写代码分析:
由于第一个和最后一个都只有一边有元素,考虑其特殊性,单独定义两个判断条件,根据其判断顺序一个放在函数前、一个放在函数后边;
定义一个循环函数,依次对其左右元素之和进行比较;
class Solution {
public int pivotIndex(int[] nums) {
if(add(nums,1,nums.length)==0){
return 0;
}
for(int i=1;i<nums.length;i++){
if(add(nums,0,i)==add(nums,i+1,nums.length)){
return i;
}
}
if(add(nums,0,nums.length-1)==0){
return nums.length-1;
}
return -1;
}
public static int add(int[] nums,int start,int end){
int count = 0;
for(int i=start;i<end;i++){
count += nums[i];
}
return count;
}
}
这个我真是醉了 一顿操作猛如虎,一看击败5%
前缀和法:
计算出列表所有和为sum
当遍历到第i个元素时,其左侧元素和为sum,右侧为total-sum-nums[i]
左右侧相等时:sum = total-sum-nums[i] 等价于: 2 * sum + nums[i] == total
class Solution {
public int pivotIndex(int[] nums) {
int total = Arrays.stream(nums).sum();
int sum = 0;
for(int i=0;i<nums.length;i++){
if (2 * sum + nums[i] == total) {
return i;
}
sum += nums[i];
}
return -1;
}
}
https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/
需求分析:
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]
自写代码分析:
可以采用双指针:
先用前指针从前往后找到第一个target的位置;
用第二个指针从后往前寻找target;
class Solution {
public int[] searchRange(int[] nums, int target) {
int first = -1,last = -1;
for(int i = 0;i<nums.length;i++){
if(nums[i]==target){
first=i;
break;
}
}
if (first!=-1){
for(int j = nums.length-1;j>=first;j--){
if(nums[j]==target){
last=j;
break;
}
}
}
int[] res = {first,last};
return res;
}
}
代码优化:
这里我有点看不懂大家的题解了,为sua子都是二分查找??? 而且都好麻烦,难道是我没理解题意嘛????罢了罢了先放着吧
https://leetcode.cn/problems/sort-array-by-parity-ii/
需求分析:
给定一个非负整数数组 nums, nums 中一半整数是 奇数 ,一半整数是 偶数 。
对数组进行排序,以便当 nums[i] 为奇数时,i 也是 奇数 ;当 nums[i] 为偶数时, i 也是 偶数输入:nums = [4,2,5,7]
输出:[4,5,2,7]
解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。
自写代码分析:
想了半天也没憋出个屁,啥也不说了,无敌for循环,找到一个换一个
class Solution {
public int[] sortArrayByParityII(int[] nums) {
for(int i=0;i<nums.length;i++){
if(i%2!=nums[i]%2){
for(int j=i+1;j<nums.length;j++){
if(nums[j]%2==i%2){
int t=nums[j];
nums[j]=nums[i];
nums[i]=t;
}
}
}
}
return nums;
}
}
代码优化思路分析:
我们可以想到如果奇数下标的值为偶数那就说明一定有一个偶数下标的值是奇数;
所以可以采用双指针的方法进行优化,定义两个指针:left = 0指向的是偶数下标;right = 1指向的是奇数下标
若i指针找到了一个奇数,那我们要保持i指针不动,令right指针在奇数下标中找到那个偶数值并且交换位置;
反之我们要令right指针不动,让left指针在偶数下标中找到那个奇数值并交换位置;
当其中一个指针遍历完其所要遍历的元素后即可返回重新排好序的数组
class Solution {
public int[] sortArrayByParityII(int[] nums) {
int left=0,right=1;
while (left<nums.length&&right<nums.length){
// left 和 right所指元素均为偶数
if (nums[left] % 2 == 0 && nums[right] % 2 == 0){
left += 2;
}
// left 和 right所指元素均为奇数
else if (nums[left] % 2 == 1 && nums[right] % 2 == 1){
right += 2;
}
// left所指元素为偶数right所指元素为奇数
else if (nums[left] % 2 == 0 && nums[right] % 2 == 1) {
left += 2;
right += 2;
}else {
int temp = nums[right];
nums[right]=nums[left];
nums[left]=temp;
left += 2;
right += 2;
}
}
return nums;
}
}
//另一种写法
class Solution {
public int[] sortArrayByParityII(int[] nums) {
int n = nums.length;
int j = 1;
for (int i = 0; i < n; i += 2) { //i遍历偶数
if (nums[i] % 2 == 1) {
while (nums[j] % 2 == 1) { //j遍历奇数
j += 2;
}
swap(nums, i, j);
}
}
return nums;
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
需求分析
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
使用时间复杂度为 O(log n) 的算法。
自写代码分析:
这不就是折半查找找到第一个大于等于它的数,返回那个数的位置;
注意考虑边界问题,如果target比所有数都大返回数组长度;
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left <= right) {
int mid = (left + right) / 2;
if(nums[mid] == target) {
return mid;
} else if(nums[mid] < target) { //考虑边界问题
left = mid + 1;
} else {
right = mid - 1;
}
}
return left;
}
}
关于链表补充知识:
https://blog.csdn.net/meini32/article/details/126248565
https://leetcode.cn/problems/reverse-linked-list/
采用头插法
建立一个虚头依次遍历结点插到新头的后边
/**
* 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 reverseList(ListNode head) {
ListNode dummyHead = new ListNode(0);
dummyHead.next=null;
ListNode p = head;
while(p!=null){
ListNode pre = p.next;
p.next=dummyHead.next;
dummyHead.next=p;
p=pre;
}
return dummyHead.next;
}
}
https://leetcode.cn/problems/swap-nodes-in-pairs/
需求分析:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题;
迭代法:
创建哑结点 dummyHead,指向头节点。
temp 表示当前到达的节点(初始时 temp = dummyHead),每次需要交换 temp 后面的两个节点,即node1、node2。
判断条件:
如果 temp 的后面没有节点或者只有一个节点,则没有更多的节点需要交换,因此结束交换。
否则,获得 temp 后面的两个节点 node1 和 node2,通过更新节点的指针关系实现两两交换节点。
具体而言,交换之前的节点关系是 temp -> node1 -> node2,交换之后的节点关系要变成 temp -> node2 -> node1,因此需要进行如下操作。
/**
* 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 dummyHead = new ListNode(0);
dummyHead.next=head;
//temp表示当前到达的节点,node1和node2在temp后代表需要替换的结点
ListNode temp = dummyHead;
while(temp.next!=null&&temp.next.next!=null){
ListNode node1 = temp.next;
ListNode node2 = temp.next.next;
//满足条件交换时,结点顺序(temp->node2->node1)
temp.next=node2;
node1.next=node2.next;
node2.next=node1;
//进行下一次循环
temp=node1;
}
return dummyHead.next;
}
}
https://leetcode.cn/problems/palindrome-linked-list/
需求分析:
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false;
回文:正着读和反着读一样,比如123321、abcba
自写代码分析:
创建两个列表i,j;
遍历链表将链表中的结点值添加到两个列表中;
采用collections自带函数反转其中一个列表并与另一个比较是否相同;
/**
* 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 boolean isPalindrome(ListNode head) {
ArrayList<Integer> i = new ArrayList<Integer>();
ArrayList<Integer> j = new ArrayList<Integer>();
ListNode p=head;
while(p!=null){
i.add(p.val);
j.add(p.val);
p=p.next;
}
Collections.reverse(i);
return i.equals(j);
}
}
快慢指针法(反转后半部分链表)
1.找到前半部分链表的尾节点。(快慢指针 快+2,慢+1)
2.反转后半部分链表。(反转链表)
3.判断是否回文。(快慢指针进行前后部分遍历)
4.恢复链表。(可有可无)
5.返回结果。
/**
* 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 boolean isPalindrome(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast.next!=null&&fast.next.next!=null){
fast=fast.next.next;
slow=slow.next;
}
ListNode newList = reverseList(slow.next);
ListNode p2 =newList;
ListNode p1 = head;
while(p2!=null){
if(p1.val!=p2.val){
return false;
}
p1=p1.next;
p2=p2.next;
}
return true;
}
//反转链表
public ListNode reverseList(ListNode head) {
ListNode dummyHead = new ListNode(0);
dummyHead.next=null;
ListNode p = head;
while(p!=null){
ListNode pre = p.next;
p.next=dummyHead.next;
dummyHead.next=p;
p=pre;
}
return dummyHead.next;
}
}
https://leetcode.cn/problems/reorder-list/
需求分析:
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
题目意思就是对单链表中的结点按照尾首顺序交差规则重排,且不能通过修改结点值来进行重排,只能通过结点交换。
自写代码分析:
这个和上个题回文链表很相似,区别是上一个是反转后半链表并比较值,这个就是反转后半链表并逐个插入到前半部分链表中间;
/**
* 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 void reorderList(ListNode head) {
ListNode fast=head,slow=head;
while(fast.next!=null&&fast.next.next!=null){
fast=fast.next.next;
slow=slow.next;
}
ListNode insertList=slow.next;
slow.next=null;
ListNode insertList1 = reverseList(insertList);
ListNode p1=insertList1;
ListNode p2=head;
while(p1!=null){
ListNode pre1=p1.next;
ListNode pre2=p2.next;
p1.next=p2.next;
p2.next=p1;
p1=pre1;
p2=pre2;
}
}
//反转函数
public ListNode reverseList(ListNode head){
ListNode dummyHead=new ListNode(0);
dummyHead.next=null;
ListNode p=head;
while(p!=null){
ListNode pre = p.next;
p.next=dummyHead.next;
dummyHead.next=p;
p=pre;
}
return dummyHead.next;
}
}
咱就是说一整个举一反三(得意) ╮( ̄▽ ̄"")╭
https://leetcode.cn/problems/linked-list-cycle/
自写代码分析:
判断链表是否有环
根据题意:如果没有环那么链表最后一个元素指向null,如果有的话就会进行无线循环;
针对无线循环,采用快慢指针遍历链表,在环形中如果指针相遇则判断有环;
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow=head,fast=head.next;
if(head==null||head.next==null){
return false;
}
while(fast!=null&&fast.next!=null){
if(fast==slow){
return true;
}
}
return false;
}
}
思路是对的,但是写法有点问题,下边这块判断是环比排除环的时间代价更高,改进一下循环判断条件:
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null||head.next==null){
return false;
}
ListNode slow=head;
ListNode fast=head.next;
while(fast!=slow){
if(fast==null||fast.next==null){
return false;
}
fast=fast.next.next;
slow=slow.next;
}
return true;
}
}
哈希表判断结点是否存在
在不考虑空间时,采用哈希表不用绕环遍历依次即可判断;
用哈希表存入结点,判断结点是否存在
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> seen = new HashSet<ListNode>();
while (head != null) {
if (!seen.add(head)) {
return true;
}
head = head.next;
}
return false;
}
}
https://leetcode.cn/problems/intersection-of-two-linked-lists/
需求分析:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
方法1:哈希集合
将A存入到哈希集合中然后遍历B,判断B中的结点是否也存在与A,返回第一个结点;
/**
* 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) {
Set<ListNode> visit =new HashSet<ListNode>();
ListNode pa = headA;
while(pa!=null){
visit.add(pa);
pa=pa.next;
}
ListNode pb = headB;
while(pb!=null){
if(visit.contains(pb)){
return pb;
}
pb=pb.next;
}
return null;
}
}
方法2:双指针
根据题意,如果两个链表相交,那么相交之后的长度差是相同的;
但是两个链表不是一样长的,所以要消除长度差,步骤为:
//pa和pb同时遍历链表A、B,其中一个到头的化就从另一个头再次开始遍历直至另一个也遍历完成;(理解的化就是假如B比A长,那A到头了,B剩下个数的不就是B前边多出来的个数,然后我们让A从新遍历B,就可以知道B的同长度起始位置)//
消除完了之后,从同一长度结点开始遍历,直到找到同一个结点或者是末尾;
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
}
https://leetcode.cn/problems/isomorphic-strings/
需求分析:
给定两个字符串 s 和 t ,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
自写代码分析:
这题我最先想到的是数字映射
将s和t中不同字母用相同的数字规则去替代,然后比较;
class Solution {
public boolean isIsomorphic(String s, String t) {
return(toNum(s).equals(toNum(t)));
}
public String toNum(String s) {
Map<String, Integer> m = new HashMap<String,Integer>();
int x = 0;
for (String b : s.split("")) {
if(!m.containsKey(b)){
m.put(String.valueOf(b),x);
x=x+1;
}
}
String ss="";
for (String i:s.split("")){
ss+=m.get(i)+",";
}
return ss;
}
}
代码优化:
这里我写的比较麻烦了,如果采用hash表其实不用那么麻烦,可以直接将t中的字符映射给s
private boolean isIsomorphicHelper(String s, String t) {
int n = s.length();
HashMap<Character, Character> map = new HashMap<>();
for (int i = 0; i < n; i++) {
char c1 = s.charAt(i);
char c2 = s.charAt(i);
if (map.containsKey(c1)) {
if (map.get(c1) != c2) {
return false;
}
} else {
map.put(c1, c2);
}
}
return true;
}
如果采用数字代替字符可以直接采用数组,效率还更高
public boolean isIsomorphic(String s, String t) {
char[] chars = s.toCharArray();
char[] chart = t.toCharArray();
int[] preIndexOfs = new int[256];
int[] preIndexOft = new int[256];
for (int i = 0; i < chars.length; i++) {
if (preIndexOfs[chars[i]] != preIndexOft[chart[i]]) {
return false;
}
preIndexOfs[chars[i]] = i + 1;
preIndexOft[chart[i]] = i + 1;
}
return true;
}
https://leetcode.cn/problems/long-pressed-name/
需求分析:
给定两个字符串name和type 判断type在去除多余字符是否等于name
官方答案:双指针
其实我想的也是双指针,但是总是搞不好第一个元素的判断处理,官方给出的答案就很巧妙,逻辑简单。
class Solution {
public boolean isLongPressedName(String name, String typed) {
//下标 i,j 分别追踪name 和typed 的位置
int i = 0, j = 0;
while (j < typed.length()) {
if (i < name.length() && name.charAt(i) == typed.charAt(j)) {
i++;
j++;
} else if (j > 0 && typed.charAt(j) == typed.charAt(j - 1)) {
j++;
} else {
return false;
}
}
// i==name.length,说明name 的每个字符都被匹配了
return i == name.length();
}
}
https://leetcode.cn/problems/backspace-string-compare/
需求分析:
给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。
自写代码分析:
感觉这就类似与入栈和出栈(先进后出)然后比较剩余的元素是否相等
创建一个数组,当碰见#就从后往前退出一个元素,如果不是就添加进去
//考虑特殊情况:开头就是#直接跳过
class Solution {
public boolean backspaceCompare(String s, String t) {
return stack(s).equals(stack(t));
}
private static ArrayList<String> stack(String s) {
String[] sList = s.split("");
ArrayList<String> sArr = new ArrayList<String>();
int l = 0;
while (l<sList.length){
if(sList[l].equals("#")&&sArr.size()!=0){
sArr.remove(sArr.size()-1);
}else if(sList[l].equals("#")&&sArr.size()==0){
l+=0;
}else {
sArr.add(sList[l]);
}
l++;
}
return sArr;
}
}
官方写法是不用转数组 采用StringBuffer
class Solution {
public boolean backspaceCompare(String S, String T) {
return build(S).equals(build(T));
}
public String build(String str) {
StringBuffer ret = new StringBuffer();
int length = str.length();
for (int i = 0; i < length; ++i) {
char ch = str.charAt(i);
if (ch != '#') {
ret.append(ch);
} else {
if (ret.length() > 0) {
ret.deleteCharAt(ret.length() - 1);
}
}
}
return ret.toString();
}
}
方法2:双指针
写的太麻烦了 看着太绕了 过过过!
关于二叉树补充知识:https://blog.csdn.net/meini32/article/details/126351612spm=1001.2014.3001.5501
递归法(前):
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
proOrder(root,res);
return res;
}
public void proOrder(TreeNode root,List<Integer> res){
if(root==null){
return;
}
res.add(root.val);
proOrder(root.left,res);
proOrder(root.right,res);
}
}
迭代:
前序遍历:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if (root == null) {
return res;
}
LinkedList<TreeNode> stack = new LinkedList<TreeNode>();//使用双向链表创建一个栈
TreeNode p = root; //创建遍历指针
while(p!=null || !stack.isEmpty()){
while(p!=null){ //从根节点一直向左遍历
res.add(p.val);
stack.push(p);//将当前结点压入栈中
p=p.left;//左孩子不空一直向左
}
p=stack.pop(); //当遍历到最左下边的结点 从栈中移除,转向结点右子树
p=p.right; //开始遍历右子树
}
return res;
}
}
中序遍历:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
LinkedList<TreeNode> stack = new LinkedList<TreeNode>(); 使用双向链表创建一个栈
TreeNode p=root; //遍历指针
while(p!=null||!stack.isEmpty()){
while(p!=null){ //一路向左并将当前结点压入栈中
stack.push(p);
p=p.left;
}
p=stack.pop(); //当到达最后一个结点 弹出并输出然后转向右子树
res.add(p.val);
p=p.right;
}
return res;
}
}
后续遍历:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
LinkedList<TreeNode> stack = new LinkedList<TreeNode>(); 使用双向链表创建一个栈
TreeNode p=root; //遍历指针
TreeNode r=null; //辅助指针指向最近访问过的
while(p!=null||!stack.isEmpty()){
while(p!=null){ //一路向左
stack.push(p);
p=p.left;
}
p = stack.pop(); //弹出二叉树最左端结点
if(p.right==null||p.right==r){ //判断结点是否遍历过且为空
res.add(p.val); //如果遍历过直接输出该节点
r=p;
p=null;
}else{ //没有遍历过就放回栈中遍历其右子树
stack.push(p);
p=p.right;
}
}
return res;
}
}
https://leetcode.cn/problems/sum-root-to-leaf-numbers/
需求分析:
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:计算从根节点到叶节点生成的 所有数字之和 。
深度优先搜索 DBF:
从根节点开始,遍历每个节点,如果遇到叶子节点,则将叶子节点对应的数字加到数字之和。如果当前节点不是叶子节点,则计算其子节点对应的数字,然后对子节点递归遍历
class Solution {
public int sumNumbers(TreeNode root) {
return dbf(root,0);
}
public int dbf(TreeNode root,int count) {
if(root==null){
return 0;
}
count=count*10+root.val; //截至当前结点的结点和
if(root.right==null&&root.left==null){ //叶节点
return count;
}else{
return dbf(root.right,count)+dbf(root.left,count); //递归i
}
}
}
…
https://leetcode.cn/problems/same-tree/
需求分析:
就是判断两个二叉树是否相同
直接递归:
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null&&q==null){
return true;
}
if(p!=null&&q!=null&&p.val==q.val){
return isSameTree(p.right,q.right)&&isSameTree(p.left,q.left);
}
return false;
}
}
https://leetcode.cn/problems/symmetric-tree/
需求分析:
给定一个二叉树判断是否从中间对称
自写代码分析:
还是递归,但是注意是结点右子树的左节点和左子树的右节点比较
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null){
return true;
}
return checkSym(root.left,root.right);
}
public boolean checkSym(TreeNode left,TreeNode right) {
if (right == null && left == null) {
return true;
}
if(left==null||right==null||right.val!=left.val){
return false;
}
return checkSym(left.left,right.right)&&checkSym(left.right,right.left);
}
}
需求分析:
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
广度优先遍历:
广度优先实现的时候,就是层层遍历,每层临时遍历的节点都会放到一个队列中。
队列中保存了第 i 层节点的信息,我们利用这个特点,将队列中的元素都串联一遍就可以了。
class Solution {
public Node connect(Node root) {
if(root==null) {
return root;
}
LinkedList<Node> queue = new LinkedList<Node>(); //采用双链表创建队列
queue.add(root);
while(!queue.isEmpty()){
//结点链接
int size = queue.size(); //当前队列的元素个数
Node p = queue.get(0); //从第一个元素开始遍历
for(int i=1;i<size;i++){
p.next=queue.get(i);
p=queue.get(i);
}
//移除父节点。添加子节点
for(int i=0;i<size;i++){
p = queue.remove();
if(p.left!=null){
queue.add(p.left);
}
if(p.right!=null){
queue.add(p.right);
}
}
}
return root;
}
}
递归:
以从上往下的方向看,1,2,3,5,6这几个节点在位置上都是紧挨着的,同时这几个节点都是左右串联的。
我们以当前节root点为起始,左右节点不断的深入下面,left节点不断往右走,right节点不断往左走,当这两个节点走到底后,整个纵深这段就完成了串联。
递归函数实现如下:
终止条件:当前节点为空时
函数内:以当前节点为起始,完成从上往下的纵深串联,再递归的调用当前节点left和right
class Solution {
public Node connect(Node root) {
if(root==null) {
return root;
}
Node pre = root;
//循环条件是当前节点的left不为空,当只有根节点
//或所有叶子节点都出串联完后循环就退出了
while(pre.left!=null) {
Node tmp = pre;
while(tmp!=null) {
//将tmp的左右节点都串联起来
//注:外层循环已经判断了当前节点的left不为空
tmp.left.next = tmp.right;
//下一个不为空说明上一层已经帮我们完成串联了
if(tmp.next!=null) {
tmp.right.next = tmp.next.left;
}
//继续右边遍历
tmp = tmp.next;
}
//从下一层的最左边开始遍历
pre = pre.left;
}
return root;
}
}
关于回溯算法的基础知识及问题题解:
回溯问题 | 题数 | 题号 |
---|---|---|
组合问题 | 5 | 77.组合、216.组合总和、17.电话号码的字母组合、39.组合总和、40.组合总和I |
分割问题 | 2 | 131.分害割回文串、93.复原IP地址 |
子集问题 | 2 | 78.子集、90.子集ll |
排列问题 | 2 | 46.全排列 |
棋盘问题 | 2 | 51.N皇后、37.解数独 |
和647.回文子串差不多是—样的
与647.回文子串和5.最长回文子串很像