797.回溯,dfs
https://leetcode-cn.com/problems/all-paths-from-source-to-target/
分析:
从0号节点开始,进行dfs,并且将访问顶点加入path列表,如果当前节点为n-1号节点,将该列表加入result结果集。
代码如下(示例):
class Solution {
List<List<Integer>> result;
LinkedList<Integer> path;
public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
result=new ArrayList<>();
path=new LinkedList<>();
dfs(graph,0);
return result;
}
//num代表遍历到哪个节点拉
//从0号节点开始,进行dfs,并且将访问顶点加入path列表,
//如果当前节点为n-1号节点,将该列表加入result结果集。
public void dfs(int[][] graph,int num){
path.add(num);
if(num==graph.length-1){
result.add(new LinkedList<>(path));
return;
}
for(int i=0;i<graph[num].length;i++){
dfs(graph,graph[num][i]);
path.removeLast();
}
}
}
进制转换
代码如下(示例):
//进制转换---》将字符串中的数转换成十进制,然后再转其他进制(2<=k<=36)
public class Solution {
public static void main(String[] args) {
System.out.println(transK("100",10,2));
System.out.println(Integer.valueOf("100",2));
}
public static String transK(String str, int x, int y){
//先将x进制对应的10进制得到,然后以10进制转其它进制
int number=Integer.valueOf(str,x);
char[] arr=new char[36];
for(int i=0;i<10;i++){
arr[i]=(char)('0'+i);
}
for(int i=10;i<36;i++){
arr[i]=(char)('A'+i-10);
}
StringBuffer sb=new StringBuffer();
while(number!=0){
sb.append(arr[number%y]);
number/=y;
}
//余数反转
return sb.reverse().toString();
}
}
232栈,队列
https://leetcode-cn.com/problems/implement-queue-using-stacks/submissions/
用栈实现队列
代码如下(示例):
class MyQueue {
//savaStack来存储,positionStack来调换位置
Stack<Integer> savaStack;
Stack<Integer> positionStack;
/** Initialize your data structure here. */
public MyQueue() {
savaStack=new Stack<>();
positionStack=new Stack<>();
}
/** Push element x to the back of queue. */
public void push(int x) {
savaStack.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if(positionStack.isEmpty()){
while(!savaStack.isEmpty()){
positionStack.push(savaStack.pop());
}
}
return positionStack.pop();
}
/** Get the front element. */
public int peek() {
if(positionStack.isEmpty()){
while(!savaStack.isEmpty()){
positionStack.push(savaStack.pop());
}
}
return positionStack.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return savaStack.isEmpty()&&positionStack.isEmpty();
}
}
用队列实现栈
https://leetcode-cn.com/problems/implement-stack-using-queues/submissions/
class MyStack {
/**
分析:队列先进先出,栈先进后出
1,元素入栈
可以创建saveQueue和positionQueue,saveQueue保存已有的数据,
让每次加进来的数据加positionQueue中使得后进来的能成为队列头,
然后将saveQueue中的元素拿出来加到positionQueue中,也就符合
栈先进后出的特点,最后将saveQueue和positionQueue互换,
此时saveQueue中的元素即为栈中的元素
*/
Queue<Integer> saveQueue;
Queue<Integer> positionQueue;
/** Initialize your data structure here. */
public MyStack() {
saveQueue=new LinkedList<>();
positionQueue=new LinkedList<>();
}
/** Push element x onto stack. */
public void push(int x) {
positionQueue.add(x);
while(!saveQueue.isEmpty()){
positionQueue.add(saveQueue.poll());
}
Queue<Integer> temp=saveQueue;
saveQueue=positionQueue;
positionQueue=temp;
}
/** Removes the element on top of the stack and returns that element. */
public int pop() {
return saveQueue.poll();
}
/** Get the top element. */
public int top() {
return saveQueue.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return saveQueue.isEmpty()&&positionQueue.isEmpty();
}
}
41
https://leetcode-cn.com/problems/first-missing-positive/
代码如下(示例):
class Solution {
public int firstMissingPositive(int[] nums) {
//由于返回第一个正整数,不包括0,所以length+1
int[] newArr=new int[nums.length+1];
//将数组元素添加到辅助数组中
for(int x:nums){
if(x>0&&x<newArr.length){
newArr[x]=x;
}
}
//遍历新数组,将缺少的数字返回
for(int i=1;i<newArr.length;i++){
if(newArr[i]!=i){
return i;
}
}
//刚好缺少最后一个正数,直接返回新数组长度
return newArr.length;
}
}
13dfs
https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/
代码如下(示例):
class Solution {
//dfs
public int movingCount(int m, int n, int k) {
boolean[][] visited=new boolean[m][n];
return dfs(visited,m,n,k,0,0);
}
public int dfs(boolean[][] visited,int m,int n,int k,int i,int j){
if(i>=m||j>=n||visited[i][j]||sums(i)+sums(j)>k){
return 0;
}
visited[i][j]=true;
return 1+dfs(visited,m,n,k,i+1,j)+dfs(visited,m,n,k,i,j+1);
}
//求位数为和
public int sums(int num){
int sum=0;
while(num>0){
sum+=num%10;
num=num/10;
}
return sum;
}
}
7递归
https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/
代码如下(示例):
class Solution {
HashMap<Integer,Integer> infixmap=new HashMap<>(); //标记中序遍历
int[] preorder;
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder=preorder;
//将中序遍历的值和索引放在infixMap中,
//方便获取左子树和右子数的数量和根节点的索引
for(int i=0;i<inorder.length;i++){
infixmap.put(inorder[i],i);
}
return recursion(0,0,preorder.length-1);
}
//前序遍历得到根节点, 中序左边界和右边界
public TreeNode recursion(int pre_root,int leftEdge,int rightEdge){
if(leftEdge>rightEdge) return null;
//获取根节点
TreeNode root=new TreeNode(preorder[pre_root]);
//获取根节点的索引,方便获取左右子树的数量
int rootIndex=infixmap.get(preorder[pre_root]);
//左子树进行递归
root.left=recursion(pre_root+1,leftEdge,rootIndex-1);
//右子数进行递归
//右子数根节点的索引=当前根节点索引+左子树数量+1
root.right=recursion(pre_root+(rootIndex-
leftEdge)+1,rootIndex+1,rightEdge);
return root;
}
}
343,dp
https://leetcode-cn.com/problems/integer-break/submissions/
相同题目:https://leetcode-cn.com/problems/jian-sheng-zi-lcof/
代码如下(示例):
class Solution {
public int integerBreak(int n) {
int[] memeory=new int[n+1];
memeory[1]=0;
memeory[2]=1;
for(int i=3;i<=n;i++){
for(int j=1;j<=i-1;j++){
memeory[i]=Math.max(memeory[i],
Math.max(j*(i-j),j*memeory[i-j]));
}
}
return memeory[n];
}
}
链接:https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/
找规律
n 乘积 组合
2 1 1 1
3 2 2
4 4 4
5 6 2 3
6 9 3 3
7 12 4 3
8 18 2 3 3
9 27 3 3 3
10 36 4 3 3
11 54 2 3 3 3
12 81 3 3 3 3
13 108 4 3 3 3
14 162 2 3 3 3 3
15 243 3 3 3 3 3
16 324 4 3 3 3 3
17 486 2 3 3 3 3 3
18 729 3 3 3 3 3 3
19 972 4 3 3 3 3 3
20 1458 2 3 3 3 3 3 3
21 2187 3 3 3 3 3 3 3
22 2916 4 3 3 3 3 3 3
23 4374 2 3 3 3 3 3 3 3
24 6561 3 3 3 3 3 3 3 3
25 8748 4 3 3 3 3 3 3 3
26 13122 2 3 3 3 3 3 3 3 3
27 19683 3 3 3 3 3 3 3 3 3
28 26244 4 3 3 3 3 3 3 3 3
29 39366 2 3 3 3 3 3 3 3 3 3
30 59049 3 3 3 3 3 3 3 3 3 3
……
//贪心算法
//最优:3.把绳子尽可能切为多个长度为 3的片段,
//留下的最后一段绳子的长度可能为0,1,2 三种情况。
public int cuttingRope(int n) {
if(n==2) return 1;
if(n==3) return 2;
long result=1;
while(n>4){
n-=3;
result=(result*3)%1000000007;
}
//最后只剩下2,3,4这几种情况
return (int)(result*n%1000000007);
}
3,滑动窗口
3,无重复字符的最长字串
https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
class Solution {
//滑动窗口
public int lengthOfLongestSubstring(String s) {
if(s.length()==0){
return 0;
}
Map<Character,Integer> map=new HashMap<>();
int maxLength=0;
int left=0;
for(int i=0;i<s.length();i++){
if(map.containsKey(s.charAt(i))){
//遇到相同的,left移到相同元素的下一个位置然后接着开始
left=Math.max(left,map.get(s.charAt(i)) + 1);
}
map.put(s.charAt(i),i);
//最长字串长度
maxLength=Math.max(maxLength,i-left+1);
}
return maxLength;
}
}
76,最小覆盖字串<滑动窗口>
https://leetcode-cn.com/problems/minimum-window-substring/
官方题解:https://www.bilibili.com/video/av883276293/
1,当前缺失得字符种类
//1,
int missType = 0;//当前缺失得字符种类数
/**
* 输入字符串为 'ABC',则 map 初始为 { A: 1, B: 1, C: 1 },
* 这些值是动态的,比如窗口新纳入一个 A,则 map["A"] 减 1。
* map["A"] 为 0 代表不缺 A 了,A 字符找齐了。
*/
Map<Character, Integer> count = new HashMap<>();
for (int i = 0; i < target.length(); i++) {
char c = target.charAt(i);
if (!count.containsKey(c)) {
missType++; //添加一种 缺失字符种类
}
/**
* Map.getOrDefault(Object key, V defaultValue)方法的作用是:
* 当Map集合中有这个key时,就使用这个key值;
* 如果没有就使用默认值defaultValue。
*/
count.put(c, count.getOrDefault(c, 0) + 1); //更新此目标字符需要的数量
}
2,窗口的扩张
扩张窗口是为了纳入目标字符,右指针右移,先找到可行解——纳入了所有目标字符。
在还没找齐目标字符之前,左指针不动。因为如果此时它右移,可能丢失现有的目标字符。
什么时候停止扩张窗口?——当前窗口包含了所有目标字符。
此时再纳入字符,条件依然满足,但徒增子串长度。
此时应该优化可行解:收窄窗口,左指针右移。
3,窗口的收缩
保持条件满足的情况下,收缩窗口是优化可行解。当窗口不再包含所有目标字符,
即有目标字符丢失,就不再收缩。
此时应该扩张窗口,补充目标字符。
可见,为了找到最优解,一直做两种操作之一,直到窗口的右端到达边界。
2,3步代码
int left = 0;
int right = 0;
int minLen = s.length() + 1; //最小窗口长度
int minStart = 0; //最小窗口的起点
for (; right < s.length(); right++) {
//2,
char c = s.charAt(right);
//右指针在扩张过程遇到了目标字符
if (count.containsKey(c)) {
count.put(c, count.get(c) - 1); //更新该目标字符仍需要的次数
if (count.get(c) == 0) {
missType--; //完成收集其中一种字符数量了
}
}
//3,
//扩张过程发现已经刚好达到目标字符串的要求,就要收缩优化,左指针向右移动
//直到不满足要求,当窗口不再包含所有目标字符,即有目标字符丢失,就不再收缩
while (missType == 0 && left <= right) {
char leftChar = s.charAt(left);
if (minLen > right - left + 1) {
minLen = right - left + 1; //更新结果的最小窗口长度
minStart = left; //更新最小窗口的字符串开始位置
}
if (count.containsKey(leftChar)) {
count.put(leftChar, count.get(leftChar) + 1);
if (count.get(leftChar) > 0) {
missType++;//当出现了缺失的目标字符,缺失种类加一
}
}
left++;//左指针向右移动,继续收缩
}
}
return minLen > s.length() ?
"" : s.substring(minStart, minStart + minLen);
题解:
class Solution {
//滑动窗口
public String minWindow(String s, String t) {
//1,当前确实的字符种类
int missType=0;//当前缺失的字符种类数
Map<Character,Integer> count=new HashMap<>();
for(int i=0;i<t.length();i++){
char c=t.charAt(i);
if(!count.containsKey(c)){
missType++;//缺失的种类++
}
//当Map集合中有这个key时,就使用这个key值,如果没有就使用默认值defaultValue。
count.put(c,count.getOrDefault(c,0)+1);
}
//2,窗口的扩张 3,窗口的收缩
int left=0; int right=0;//左右指针
int minLen=s.length()+1;//最小窗口长度
int minStart=0;//最小窗口的起点
for(;right<s.length();right++){
//2,窗口的扩张
char c=s.charAt(right);
//右指针在扩张过程中遇到了目标字符
if(count.containsKey(c)){
count.put(c,count.get(c)-1);//更新该目标字符仍需要的次数
if(count.get(c)==0){
missType--;//缺失的种类找到一个,减少一种
}
}
//3,窗口的收缩,扩张过程发现已达到目标字符串的要求,就要收缩优化,左指针右移优化
while(missType==0&&left<=right){
char leftChar=s.charAt(left);
if(minLen>right-left+1){
minLen=right-left+1;//更新结果最小长度
minStart=left;//更新最小窗口的字符串开始位置
}
if(count.containsKey(leftChar)){
count.put(leftChar,count.get(leftChar)+1);
if(count.get(leftChar)>0){
missType++;//缺失的种类++
}
}
left++;//左指针向右移动,继续收缩
}
}
return minLen>s.length()?"":s.substring(minStart,minStart+minLen);
}
}
239,滑动窗口最大值
https://leetcode-cn.com/problems/sliding-window-maximum/
暴力法:超时
class Solution {
//暴力法
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayList<Integer> list=maxInWindowArr(nums,k);
int[] result = new int[list.size()];
for(int i = 0;i<list.size();i++){
result[i] = list.get(i);
}
return result;
}
//滑动窗口最大值的集合
public ArrayList<Integer> maxInWindowArr(int[] nums,int size){
ArrayList<Integer> list=new ArrayList<>();
if(size<1||nums.length<size) return list;
int left=0;
int right=size-1;
while(right<nums.length){
int maxInWindow=maxInWindow(left,right,nums);
list.add(maxInWindow);
left++;
right++;
}
return list;
}
//滑动窗口中的最大值
public int maxInWindow(int left,int right,int[] nums){
int maxInWindow=nums[left];
for(int i=left;i<=right;i++){
if(maxInWindow<nums[i]) maxInWindow=nums[i];
}
return maxInWindow;
}
}
单调队列:
class Solution {
//单调队列
public int[] maxSlidingWindow(int[] nums, int k) {
int[] result=new int[nums.length-k+1];
int size=k;
int count=0;
Deque<Integer> deque=new LinkedList<>();
for(int i=0;i<nums.length;i++){
//1,删除队列尾部,只要比加入的元素小就删除
while(!deque.isEmpty()&&nums[deque.getLast()]<nums[i]){
deque.pollLast();
}
//加入元素
deque.addLast(i);
//2,删除队列头部<不在窗口中的元素>
while(!deque.isEmpty()&&deque.getFirst()<=i-size){
deque.pollFirst();
}
//添加到结果集
if(i>=size-1){
result[count++]=nums[deque.getFirst()];
}
}
return result;
}
}
59,螺旋矩阵Ⅱ
https://leetcode-cn.com/problems/spiral-matrix-ii/
class Solution {
//暴力法:模拟整个向内环绕的填入过程
public int[][] generateMatrix(int n) {
//矩阵初始化,边界初始化
int[][] result=new int[n][n];
int left=0;
int right=result[0].length-1;
int up=0;
int down=result.length-1;
//起始数字和终止数字
int start=1;
int target=n*n;
//循环填入数字
while(start<=target){
//从左向右填充
for(int i=left;i<=right;i++){
result[up][i]=start;
start++;
}
up++;//收缩边界
//从上到下填充
for(int i=up;i<=down;i++){
result[i][right]=start;
start++;
}
right--;//收缩边界
//从右到左
for(int i=right;i>=left;i--){
result[down][i]=start;
start++;
}
down--;//边界收缩
//从下到上
for(int i=down;i>=up;i--){
result[i][left]=start;
start++;
}
left++;//边界收缩
}
return result;
}
}
螺旋矩阵Ⅰ
链接:https://leetcode-cn.com/problems/spiral-matrix/
题解及分析:
class Solution {
//暴力法:模拟整个向内环绕的填入过程
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> result = new ArrayList<Integer>();
if(matrix==null||matrix.length==0) return result;
int left=0;
int right=matrix[0].length-1;
int up=0;
int down=matrix.length-1;
//终止数字
int start=1;
int target=matrix.length* matrix[0].length;
//循环填入数字
while(start<=target){
//从左向右扫描,并加入到result中
for(int i=left;i<=right&&start<=target;i++){
result.add(matrix[up][i]);
start++;
}
up++;//收缩边界
//从上到下扫描
for(int i=up;i<=down&&start<=target;i++){
result.add(matrix[i][right]);
start++;
}
right--;//收缩边界
//从右到左扫描
for(int i=right;i>=left&&start<=target;i--){
result.add(matrix[down][i]);
start++;
}
down--;//边界收缩
//从下到上扫描
for(int i=down;i>=up&&start<=target;i--){
result.add(matrix[i][left]);
start++;
}
left++;//边界收缩
}
return result;
}
}
小练习
一,删除所有重复的节点
力扣:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/
public ListNode deleteDuplicates(ListNode head) {
ListNode dummy=new ListNode(-1,head);
ListNode cur=dummy;
while(cur.next!=null&&cur.next.next!=null){
if(cur.next.val==cur.next.next.val){
//保存这个相同的值
int same=cur.next.val;
while(cur.next!=null&&cur.next.val==same){
cur.next=cur.next.next;
}
}else{
//值不同
cur=cur.next;
}
}
return dummy.next;
}
二,求某一个数字在单条路径中出现最多的次数
力扣:https://leetcode-cn.com/problems/longest-univalue-path/submissions/
class TreeNode{
int val;
TreeNode left;
TreeNode right;
TreeNode(int x){val=x;}
public void setLeft(TreeNode left) {
this.left = left;
}
public void setRight(TreeNode right) {
this.right = right;
}
}
public class Main {
static int max = 0;
public static void main(String[] args) {
TreeNode root=new TreeNode(2);
TreeNode leftroot=new TreeNode(3);
TreeNode rightroot=new TreeNode(1);
TreeNode leftrootleft=new TreeNode(3);
TreeNode leftrootright=new TreeNode(1);
TreeNode rightrootright=new TreeNode(1);
root.setLeft(leftroot);
root.setRight(rightroot);
leftroot.setLeft(leftrootleft);
leftroot.setRight(leftrootright);
rightroot.setRight(rightrootright);
System.out.println(longestUnivaluePath(root));
}
public static int longestUnivaluePath(TreeNode root) {
if (root == null) return 0;
max = 0;
longestPath(root);
return max+1;
}
/**
*找二叉树的最长相同节点路径数,对于每个节点来说,都可以将自己看作根节点,可以独立的看为一棵树,
*(1)它的最长路径数要么是左子树的最长路径数,
*(2)要么是右子树的最长路径数,
*(3)要么就是左右子树的路径数相加(当然前提是左右子树的节点值和根节点值相等),这样用递归来解。
* 求二叉树的最长相同节点个数:及就是在最长路径的基础上+1,还要去掉第三步左右子树的值跟根节点相等
*/
//递归函数的功能其实就是从左子树和右子树中返回一个最大的同值路径
public static int longestPath(TreeNode root){
int leftLength=0;
int rightLength=0;
//(1)
if(root.left!=null&&root.val==root.left.val){
leftLength=1+longestPath(root.left);
}else if(root.left!=null){
longestPath(root.left);
}
//(2)
if(root.right!=null&&root.val==root.right.val){
rightLength=1+longestPath(root.right);
}else if(root.right!=null){
longestPath(root.right);
}
//(3)更新max,去掉左右子树相等的情况即可得到最长相同节点路径的个数
if((leftLength+rightLength)>max&&root.left.val!=root.right.val){
max=leftLength+rightLength;
}
return leftLength>rightLength?leftLength:rightLength;//每次返回最长的一条路
}
}
三,相乘后的最大结果
public class Main {
//DFS
public static void main(String[] args) {
int[] arr = new int[]{8,7,5,4,3,2};
System.out.println(maxNum(arr));
}
static int res;
static LinkedList<Integer> num1;
static LinkedList<Integer> num2;
public static int maxNum(int[] arr){
//两个数首位已确定
num1=new LinkedList<>();
num2=new LinkedList<>();
num1.addLast(arr[0]);
num2.addLast(arr[1]);
res=0;
dfs(arr,2);//从第二个索引开始dfs
return res;
}
private static void dfs(int[] arr, int index) {
//两个三位数得到,将两个三位数相乘,每次将最大值进行更新
if(index==arr.length){
int maxSum1=0;
int maxSum2=0;
//拿到maxSum1
for(int i:num1){
maxSum1*=10;
maxSum1+=i;
}
//拿到maxSum2
for(int i:num2){
maxSum2*=10;
maxSum2+=i;
}
res=Math.max(res,maxSum1*maxSum2);
return;
}
//DFS
if(num1.size()==num2.size()){
//如果两个数位数相同,则其都要进行dfs
//先对num1dfs
num1.addLast(arr[index]);
dfs(arr,index+1);
num1.removeLast();
//再对num2dfs
num2.addLast(arr[index]);
dfs(arr,index+1);
num2.removeLast();
}else {
if(num1.size()>num2.size()){
num2.addLast(arr[index]);
dfs(arr,index+1);
num2.removeLast();
}else{
num1.addLast(arr[index]);
dfs(arr,index+1);
num1.removeLast();
}
}
}
}
50,Pow(x,n)
https://leetcode-cn.com/problems/powx-n/
1,暴力法:超时
代码如下(示例):
//思路1:循环计算 n次 x相乘,时间复杂度O(n), 当n非常大时,效率低
public static double myPow(double x, int n) {
if(n==0) return 1.0;
double result = 1;
if (n >0) {
for (int i = 0; i < n; i++) {
result*=x;
}
} else {
n=-n;
for (int i = 0; i < n; i++) {
result*=x;
}
result=1 / result;
}
return result;
}
2,快速幂–>实际上是分治思想
每次递归都会使得指数减少一半,因此递归的层数为 O(\log n)O(logn),
算法可以在很快的时间内得到结果。
代码:
public double myPow(double x, int n) {
if(n==Integer.MIN_VALUE)
return (x==1||x==-1) ?1:0;
else if(n==0) return 1;
if(n<0) return myPow(1/x,-n);
if(n%2==0) return myPow(x*x,n/2);
else return x*myPow(x,n-1);
}
372,超级次方
https://leetcode-cn.com/problems/super-pow/
class Solution {
//快速幂
static int base=1337;
//将数组元素放入栈中
public int superPow(int a, int[] b) {
Stack<Integer> stack=new Stack<>();
int n=b.length;
for(int i=0;i<n;i++){
stack.push(b[i]);
}
return recursion(a,stack);
}
//缩小规模递归求解
public int recursion(int a,Stack<Integer> stack){
if(stack.isEmpty()) return 1;
int last=stack.pop();
int part1=myPower(a,last);
int part2=myPower(recursion(a,stack),10);
return part1*part2%base;
}
//a^k
public int myPower(int a,int k){
int res=1;
a%=base;
for(int i=0;i<k;i++){
res*=a;
res%=base;
}
return res;
}
}
20表示字符的字符串
链接:https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/
代码及分析:
class Solution {
public boolean isNumber(String s) {
int index=0;//字符串索引
int sLength=s.length();
boolean hasNum=false; boolean hasE=false;
boolean hasSign=false;// + -
boolean hasDot=false;// .
//1,先处理开头空格,index后移
while(index<sLength&&s.charAt(index)==' '){
index++;
}
//2,遍历字符串,判断字符串是否表示数值
while(index<sLength){
/*
(1)数字: hasNum=true;
index++ 到非数字 或者遍历到末尾位置
if(index==n) 结束循环
*/
while(index<sLength&&s.charAt(index)>='0'&&s.charAt(index)<='9'){
index++;
hasNum=true;
}
if(index==sLength){
break;
}
char c=s.charAt(index);
/*
(2)e/E: e已经出现 或者e之前无数字,false 否则 hasE=true;
并且将其他三个flag置为false,开始遍历e后面的新的一部分数字
*/
if(c=='e'||c=='E'){
if(hasE||!hasNum){
return false;
}
hasE=true;
hasDot=false; hasNum=false; hasSign=false;
}else if(c=='+'||c=='-'){
/*
(3)+/-: 如果已经出现过+/- 或者已经出现过数字或者已经出现过'.',返回flase;
否则令hasSign = true
*/
if(hasSign||hasDot||hasNum){
return false;
}
hasSign=true;
}else if(c=='.'){
/*
(4). 如果已经出现过'.'或者已经出现过'e'或'E',返回false;
否则令hasDot = true
*/
if(hasDot||hasE){
return false;
}
hasDot=true;
}else if(c==' '){
break;
/*
(5)空 如果到末尾的空格了,结束循环;
但也有可能是字符串中间的空格,在循环外继续处理
*/
}else{
/*
(6)其他情况返回false
*/
return false;
}
index++;
}
//3,处理空格,index相应的后移
while(index<sLength&&s.charAt(index)==' '){
index++;
}
//4,if(index==sLength) 遍历到了末尾; 但还要满足hasNum=true;
//因为如果字符串全是符号而没有数字是不行的,而且e后面没有数字不行,但没有符号可以
return hasNum&&index==sLength;
}
}
28对称的二叉树
链接:https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/
分析:
代码:
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null){
return true;
}
return symmetry(root.left,root.right);
}
public boolean symmetry(TreeNode leftNode, TreeNode rightNode){
//如果左右节点都为空,就对称
if(leftNode==null&&rightNode==null) return true;
//如果左右节点其中一个为空,或者左边节点的值与对称位置的值不相等,就不对称
if(leftNode==null||rightNode==null||leftNode.val!=rightNode.val) return false;
return symmetry(leftNode.left,rightNode.right)&&symmetry(leftNode.right,rightNode.left);
}
}
27二叉树的镜像
链接:https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/
代码:
//根据二叉树镜像的定义,考虑递归遍历(dfs)二叉树,交换每个节点的左 / 右子节点,即可生成二叉树的镜像。
class Solution {
//递归
public TreeNode mirrorTree(TreeNode root) {
if(root==null){
return null;
}
TreeNode leftNode=mirrorTree(root.right);
TreeNode rightNode=mirrorTree(root.left);
root.left=leftNode;
root.right=rightNode;
return root;
}
}
26树的子结构
链接:https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/
代码及分析:
class Solution {
//我们只需要从根节点开始判断,通过递归的方式比较他的每一个子节点即可
public boolean isSubStructure(TreeNode A, TreeNode B) {
//边界值判断,如果A B有一个为空,就返回false
if(A==null||B==null){
return false;
}
//B不光有可能是A的子结构,也有可能是A左子树的子结构或者右子树的子结构,
//所以如果从根节点判断B不是A的子结构,还要继续判断B是不是A左子树的子结构和右子树的子结构
return isSubStructure1(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
}
//判断B是不是A的子结构
public boolean isSubStructure1(TreeNode A, TreeNode B){
//如果B为空,说明B访问完了,确定是A的子结构
if(B==null){
return true;
}
//如果B不为空,A为空,或者两个节点值不同,说明B不是A的子节点
if(A==null||A.val!=B.val){
return false;
}
//当前节点判断完,换要判断左右子节点
return isSubStructure1(A.left,B.left)&&isSubStructure1(A.right,B.right);
}
}
152. 乘积最大子数组
链接:https://leetcode-cn.com/problems/maximum-product-subarray/
错误:
分析及题解:
class Solution {
/* dp:
(1)遍历数组时计算当前最大值,不断更新
(2)令imax为当前最大值,则当前最大值为 imax = max(imax * nums[i], nums[i])
由于存在负数,那么会导致最大的变最小的,最小的变最大的。因此还需要维护当前最小值imin,imin = min(imin * nums[i], nums[i])
(3)当负数出现时则imax与imin进行交换再进行下一步计算
*/
public int maxProduct(int[] nums) {
int max=Integer.MIN_VALUE;//结果最大值
int imax=1; int imin=1;
for(int i=0;i<nums.length;i++){
//(3)
if(nums[i]<0){
int temp=imax;
imax=imin;
imin=temp;
}
//(1) (2)
imax=Math.max(imax*nums[i],nums[i]);
imin=Math.min(imin*nums[i],nums[i]);
//更新max
max=Math.max(max,imax);
}
return max;
}
}
Offer 19. 正则表达式匹配(dp)
链接:https://leetcode-cn.com/problems/zheng-ze-biao-da-shi-pi-pei-lcof/
分析:
代码:
class Solution {
//dp
public boolean isMatch(String A, String B) {
int n = A.length();
int m = B.length();
boolean[][] f = new boolean[n + 1][m + 1];
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
if(j == 0){
//如果B为空正则, A为空正则 0==0 返回true; 否则返回false
f[i][j] = i == 0;
}else{
//B不为空正则
if (B.charAt(j - 1) != '*') {
//(1)B的最后一个字符是正常字符 || B的最后一个字符是.
if (i > 0 && (A.charAt(i - 1) == B.charAt(j - 1) || B.charAt(j - 1) == '.')){
f[i][j] = f[i - 1][j - 1];
}
}else{
//(2)如果B的最后一个字符是*它代表 B[m-2]=c可以重复0次或多次,它们是一个整体 c*
//1,
if (i >= 1 && j >= 2 && (A.charAt(i - 1) == B.charAt(j - 2)
|| B.charAt(j - 2) == '.')) {
f[i][j] |= f[i - 1][j];
}
//2, A[i-1]是0个c
if (j >= 2) {
f[i][j] |= f[i][j - 2];
}
}
}
}
}
return f[n][m];
}
}
Offer 35. 复杂链表的复制
链接:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/
分析:
代码及分析:
class Solution {
public Node copyRandomList(Node head) {
if(head==null) return null;
Node cur=head;
// 1,复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
Map<Node,Node> map=new HashMap<>();
while(cur!=null){
map.put(cur,new Node(cur.val));
cur=cur.next;
}
cur=head;
//2,构建新链表的 next 和 random 指向
while(cur!=null){
map.get(cur).next=map.get(cur.next);
map.get(cur).random=map.get(cur.random);
cur=cur.next;
}
//3,返回新链表的头节点
return map.get(head);
}
}
Offer 41. 数据流中的中位数
链接:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/
分析及代码 小顶堆,大顶堆
class MedianFinder {
/*
1,输入的时候将数字分为两半,小的一半放在大根堆small中,大的一半放在小根堆的big中。
2,输入的同时保证两堆的大小之差不超过一,如果超过,则将数量多的堆弹出堆顶元素放到另一个堆中。
3,取中位数的时候,奇数返回数量多的堆顶元素;偶数返回两堆的堆顶平均数即可。
*/
/** initialize your data structure here. */
Queue<Integer> big, small;
int count; //数字数量
public MedianFinder() {
big = new PriorityQueue<>(); // 小顶堆,保存较大的一半
small = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
count=0;
}
public void addNum(int num) {
//1,
if(small.isEmpty()){
small.add(num);
count++;
return;
}
if(num<=small.peek()){
small.add(num);
count++;
}else{
big.add(num);
count++;
}
//2,
if(small.size()-big.size()==2){
big.add(small.poll());
}
if(big.size()-small.size()==2){
small.add(big.poll());
}
}
public double findMedian() {
//3,
if(count%2!=0){
if(small.size()>big.size()){
return small.peek();
}else{
return big.peek();
}
}else{
return (small.peek() + big.peek()) * 0.5;
}
}
}
Offer 36. 二叉搜索树与双向链表
链接:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/
分析:
分析及代码
//中序遍历递归
class Solution {
//记录头结点
private Node head = null;
//记录遍历当前节点的前一个节点,用来把当前节点和前一个节点给串起来的
private Node pre = null;
public Node treeToDoublyList(Node root) {
if(root==null) return root;
inorder(root);
//2,上面将链表各节点进行了双向连接,下面将链表的首尾连接起来
head.left=pre;
pre.right=head;
return head;
}
//二叉树的中序遍历
private void inorder(Node root) {
if(root==null) return;//边界条件的判断
//先遍历左子节点
inorder(root.left);
//1,将链表各节点进行双向连接
if (pre == null) {
//当前节点是头节点
head = root;
} else {
//串起来的结果就是前一个节点pre的right指向当前节点,
//然后当前节点的left指向前一个节点pre
pre.right = root;
}
root.left = pre;
//前一个节点和当前节点串起来之后,就让前一个节点指向当前节点,向后遍历
pre = root;
//最后在遍历右子节点
inorder(root.right);
}
}
栈的压入、弹出序列
链接:https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/
分析及代码
class Solution {
//入栈操作: 按照压栈序列的顺序执行。
//出栈操作: 每次入栈后,循环判断 “栈顶元素 == 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出。
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack=new Stack<>();
int i=0;
for(int num:pushed){
stack.push(num);//入栈
while(!stack.isEmpty()&&popped[i]==stack.peek()){
stack.pop();
i++;
}
}
return stack.isEmpty();
}
}
Offer 38. 字符串的排列
链接:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/submissions/
分析及代码:
class Solution {
/*
dfs:
当前固定位 x
1,剪枝:若 c[i] 在 Set 中,代表其是重复字符,因此 “剪枝” ;
2,将 c[i] 加入 Set ,以便之后遇到重复字符时剪枝;
3,固定字符: 将字符 c[i] 和 c[x] 交换,即固定 c[i] 为当前位字符;
4,开启下层递归: 调用 dfs(x + 1) ,即开始固定第 x + 1 个字符;
5,还原交换: 将字符 c[i] 和 c[x] 交换(还原之前的交换);
*/
List<String> res = new LinkedList<>();
char[] c;
public String[] permutation(String s) {
c = s.toCharArray();
dfs(0);
return res.toArray(new String[res.size()]);
}
//从当前位置进行dfs
public void dfs(int x){
if(x==c.length-1){
res.add(String.valueOf(c));//添加一种排列方案
return;
}
HashSet<Character> set = new HashSet<>();
for(int i=x;i<c.length;i++){
//1,如果重复就剪枝
if(set.contains(c[i])) continue;
//2,
set.add(c[i]);
//3,交换,将c[i]固定在第x位
swap(i,x);
//4,固定第x+1位
dfs(x+1);
//5,恢复交换
swap(i,x);
}
}
//交换两个位置的值
public void swap(int a,int b){
char temp=c[a];
c[a]=c[b];
c[b]=temp;
}
}
Offer 37. 序列化二叉树
链接:https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/
分析及代码:
/*
通常使用的前序、中序、后序、层序遍历记录的二叉树的信息不完整,即唯一的输出序列可能对应着多种二叉树可能性。
题目要求的 序列化 和 反序列化 是 可逆操作。因此,序列化的字符串应携带完整的二叉树信息 。
*/
public class Codec {
/*
借助队列,对二叉树做层序遍历,并将越过叶节点的 null 也打印出来。
1,特例处理:若 root 为空,则直接返回空列表 "[]" ;
2,初始化:队列 queue (包含根节点 root );序列化列表 res ;
3,层序遍历:当 queue 为空时跳出;
节点出队列,记为 node ;
若 node 不为空:(1)打印字符串 node.val (2) 将左、右子节点加入 queue ;
否则(若 node 为空):打印字符串 "null" ;
4,返回值: 拼接列表,用 ',' 隔开,首尾添加中括号;
*/
public String serialize(TreeNode root) {
//1,
if(root == null) return "[]";
//2,
StringBuilder res = new StringBuilder("[");
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
//3,
while(!queue.isEmpty()) {
TreeNode node = queue.poll();
if(node != null) {
res.append(node.val + ",");
queue.add(node.left);
queue.add(node.right);
}
else res.append("null,");
}
//4,
res.deleteCharAt(res.length() - 1);
res.append("]");
return res.toString();
}
/*
利用队列按层构建二叉树,借助一个指针i指向节点 node 的左、右子节点,每构建一个 node 的左、右子节点,指针i 就向右移动1位。
1,特例处理:若 data 为空,直接返回 null
2,初始化:序列化列表 vals (先去掉首尾中括号,再用逗号隔开),指针 i = 1 ,根节点 root (值为 vals[0] ),队列 queue(包含 root );
3,按层构建: 当 queue 为空时跳出;
节点出队,记为 node ;
构建 node 的左子节点:node.left 的值为 vals[i] ,并将 node.left 入队;
执行 i += 1 ;
构建 node 的右子节点:node.left 的值为 vals[i] ,并将 node.left 入队;
执行 i += 1 ;
4,返回值: 返回根节点 root 即可;
*/
public TreeNode deserialize(String data) {
//1,
if(data.equals("[]")) return null;
//2,
String[] vals = data.substring(1, data.length() - 1).split(",");
TreeNode root = new TreeNode(Integer.parseInt(vals[0]));
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int i = 1;
//3,
while(!queue.isEmpty()) {
TreeNode node = queue.poll();
if(!vals[i].equals("null")) {
node.left = new TreeNode(Integer.parseInt(vals[i]));
queue.add(node.left);
}
i++;
if(!vals[i].equals("null")) {
node.right = new TreeNode(Integer.parseInt(vals[i]));
queue.add(node.right);
}
i++;
}
//4,
return root;
}
}
三数之和
链接:https://leetcode-cn.com/problems/3sum/
分析及代码:
class Solution {
/*
双指针法思路:固定3个指针中最左(最小)数字的指针 k,双指针 i,j 分设在数组索引 (k, len(nums))两端,通过双指针交替向中间移动,记录对于每个固定指针 k 的所有满足 nums[k] + nums[i] + nums[j] == 0 的 i,j 组合:
*/
public List<List<Integer>> threeSum(int[] nums) {
/*
1,当 nums[k] > 0 时直接break跳出:因为 nums[j] >= nums[i] >= nums[k] > 0,即3个数字都大于0,在此固定指针 k 之后不可能再找到结果了。
2,当 k > 0且nums[k] == nums[k - 1]时即跳过此元素nums[k]:因为已经将 nums[k - 1] 的所有组合加入到结果中,本次双指针搜索只会得到重复组合。
3,i,j 分设在数组索引 (k, len(nums))(k,len(nums)) 两端,当i < j时循环计算s = nums[k] + nums[i] + nums[j],并按照以下规则执行双指针移动:
当s < 0时,i += 1并跳过所有重复的nums[i];
当s > 0时,j -= 1并跳过所有重复的nums[j];
当s == 0时,记录组合[k, i, j]至res,执行i += 1和j -= 1并跳过所有重复的nums[i]和nums[j],防止记录到重复组合。
*/
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for(int k=0;k<nums.length-2;k++){
//1.
if(nums[k]>0) break;
//2,
if(k>0&&nums[k]==nums[k-1]) continue;
//3,
int i=k+1; int 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;
}
}
Offer 32 - I. 从上到下打印二叉树
链接:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/
class Solution {
/*
BFS 循环: 当队列 queue 为空时跳出;
1,出队: 队首元素出队,记为 node;
2,打印: 将 node.val 添加至res 尾部;
3,添加子节点: 若 node 的左(右)子节点不为空,则将左(右)子节点加入队列 queue ;
*/
public int[] levelOrder(TreeNode root) {
if(root==null) return new int[0];
Queue<TreeNode> queue=new LinkedList<>(){
{add(root);} };
ArrayList<Integer> list=new ArrayList<>();
while(!queue.isEmpty()){
//1,
TreeNode node=queue.poll();
//2,
list.add(node.val);
//3,
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
int[] res=new int[list.size()];
for(int i=0;i<list.size();i++)
res[i]=list.get(i);
return res;
}
}
Offer 32 - II. 从上到下打印二叉树 II
链接:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/
class Solution {
/*
BFS 循环: 当队列 queue 为空时跳出;
1,新建一个临时列表 tmp ,用于存储当前层打印结果;
2,当前层打印循环: 循环次数为当前层节点数(即队列 queue 长度);
出队: 队首元素出队,记为 node;
打印: 将 node.val 添加至 tmp 尾部;
添加子节点: 若 node 的左(右)子节点不为空,则将左(右)子节点加入队列 queue ;
3,将当前层结果 tmp 添加入 res 。
*/
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue=new LinkedList<>();
List<List<Integer>> res=new ArrayList<>();
if(root!=null) queue.add(root);
while(!queue.isEmpty()){
//1,
List<Integer> temp=new ArrayList<>();
//2,
for(int i=queue.size();i>0;i--){
TreeNode node=queue.poll();
temp.add(node.val);
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
res.add(temp);
}
return res;
}
}
Offer 32 - III. 从上到下打印二叉树 III
链接:https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue=new LinkedList<>();
List<List<Integer>> res=new ArrayList<>();
if(root!=null) queue.add(root);
int count=0;
while(!queue.isEmpty()){
//1,
List<Integer> temp=new ArrayList<>();
//2,
for(int i=queue.size();i>0;i--){
TreeNode node=queue.poll();
temp.add(node.val);
if(node.left!=null) queue.add(node.left);
if(node.right!=null) queue.add(node.right);
}
count++;
if(count%2==0){
//如果count是偶数就反转单链表
Collections.reverse(temp);
}
res.add(temp);
}
return res;
}
}
字形变换
链接:https://leetcode-cn.com/problems/zigzag-conversion/
class Solution {
/*
算法流程: 按顺序遍历字符串 s;
1,res[i] += c: 把每个字符 c 填入对应行res[i];
2,i += flag: 更新当前字符 c 对应的行索引;
3,flag = - flag: 在达到 ZZ 字形转折点时,执行反向。
*/
public String convert(String s, int numRows) {
if(numRows<2) return s;
List<StringBuilder> rows=new ArrayList<>();
//几行
for(int i=0;i<numRows;i++) rows.add(new StringBuilder());
int i=0; int flag=-1;
for(char c:s.toCharArray()){
rows.get(i).append(c);
if(i==0||i==numRows-1) flag=-flag;
i+=flag;
}
StringBuilder res=new StringBuilder();
for(StringBuilder row:rows) res.append(row);
return res.toString();
}
}
451. 根据字符出现频率排序
链接:https://leetcode-cn.com/problems/sort-characters-by-frequency/
分析及代码:
class Solution {
/*
1.使用哈希表记录每个字符出现的频率
2.再将列表中的字符按照频率降序排序。
3.遍历顺序为字符按照频率递减的顺序。对于每个字符,将该字符按照出现频率拼接到排序后的字符串
*/
public String frequencySort(String s) {
//1
Map<Character,Integer> map=new HashMap<>();
int frequency=0;
for(int i=0;i<s.length();i++){
char c=s.charAt(i);
frequency=map.getOrDefault(c,0)+1;
map.put(c,frequency);
}
//2
List<Character> list=new ArrayList<>(map.keySet());
Collections.sort(list,(a,b)->
map.get(b)-map.get(a)
);
//3
StringBuilder sb=new StringBuilder();
for(int i=0;i<list.size();i++){
char c=list.get(i);
frequency=map.get(c);
for(int j=0;j<frequency;j++){
sb.append(c);
}
}
return sb.toString();
}
}
字典树
链接:https://leetcode-cn.com/problems/implement-trie-prefix-tree/
前缀树的3个基本性质:
1,根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2,从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3,每个节点的所有子节点包含的字符都不相同。
通常字典树的查询时间复杂度是O(logL),L是字符串的长度。
字典树(tries树):
对于单词查询这种,还是用字典树比较好,但也是有前提的,空间大小允许,字典树的空间相比较hash还是比较浪费的,毕竟hash可以用bit数组。
那么在空间要求不那么严格的情况下,字典树的效率不一定比hash弱,它支持动态查询,比如apple,当用户输入到appl时,字典树此刻的查询位置可以就到达l这个位置,那么我在输入e时,光查询e就可以了(更何况如果我们直接用字母的ASCII作下标肯定会更快)!字典树它并不用等待你完全输入完毕后才查询。
208. 实现 Trie (前缀树)
分析及代码:
class Trie {
private Trie[] children;
private boolean isEnd;
public Trie() {
children=new Trie[26];
isEnd=false;
}
/*
从字典树的根开始,插入字符串。对当前字符对应的子节点,有两种情况:
(1)子节点存在。沿着指针移动到子节点,继续处理下一个字符。
(2)子节点不存在。创建一个新的子节点,记录在children 数组的对应位置上,然后沿着指针移动到子节点,继续搜索下一个字符。
重复以上步骤,直到处理字符串的最后一个字符,然后将当前节点标记为字符串的结尾。
*/
public void insert(String word) {
Trie node=this;
for(int i=0;i<word.length();i++){
char ch=word.charAt(i);
int index=ch-'a';
if(node.children[index]==null){
node.children[index]=new Trie();
}
node=node.children[index];
}
node.isEnd=true;
}
public boolean search(String word) {
Trie node=searchPrefix(word);
return node!=null&&node.isEnd;
}
public boolean startsWith(String prefix) {
return searchPrefix(prefix)!=null;
}
/*
我们从字典树的根开始,查找前缀。对于当前字符对应的子节点,有两种情况:
(1)子节点存在。沿着指针移动到子节点,继续搜索下一个字符。
(2)子节点不存在。说明字典树中不包含该前缀,返回空指针。
重复以上步骤,直到返回空指针或搜索完前缀的最后一个字符。
若搜索到了前缀的末尾,就说明字典树中存在该前缀。此外,若前缀末尾对应节点的isEnd 为真,则说明字典树中存在该字符串。
*/
private Trie searchPrefix(String prefix){
Trie node=this;
for(int i=0;i<prefix.length();i++){
char ch=prefix.charAt(i);
int index=ch-'a';
if(node.children[index]==null){
return null;
}
node=node.children[index];
}
return node;
}
}
211. 添加与搜索单词
链接:https://leetcode-cn.com/problems/design-add-and-search-words-data-structure/
分析及代码:
class WordDictionary {
private Trie root;
public WordDictionary() {
root=new Trie();
}
public static void main(String[] args) {
WordDictionary wordDictionary = new WordDictionary();
wordDictionary.addWord("bad");
wordDictionary.addWord("dad");
wordDictionary.addWord("mad");
System.out.println(wordDictionary.search("pad")); //false
System.out.println(wordDictionary.search("bad")); //true
System.out.println(wordDictionary.search(".ad")); //true
System.out.println(wordDictionary.search("b..")); //true;
}
public void addWord(String word) {
root.insert(word);
}
public boolean search(String word) {
return dfs(word,0,root);
}
/*
对于搜索单词,从字典树的根结点开始搜索。由于待搜索的单词可能包含点号,因此在搜索过程中需要考虑点号
(1)如果当前字符是字母,则判断当前字符对应的子结点是否存在,如果子结点存在,则移动到子结点,继续搜索下一个字符,如果子结点不存在则说明单词不存在,返回false;
(2)如果当前字符是点号,由于点号可以表示任何字母,因此需要对当前结点的所有非空子结点继续搜索下一个字符。
重复上述步骤,直到返回false或搜索完给定单词的最后一个字符。
如果搜索完给定的单词的最后一个字符,则当搜索到的最后一个结点的isEnd 为true 时,给定的单词存在。
特别地,当搜索到点号时,只要存在一个非空子结点可以搜索到给定的单词,即返回true。
*/
private boolean dfs(String word,int index,Trie node){
if(index==word.length()){
return node.isEnd();
}
char ch=word.charAt(index);
if(Character.isLetter(ch)){
int childrenIndex=ch-'a';
Trie child=node.getChildren()[childrenIndex];
if(child!=null&&dfs(word,index+1,child)){
return true;
}
}else{
for(int i=0;i<26;i++){
Trie child=node.getChildren()[i];
if(child!=null&&dfs(word,index+1,child)){
return true;
}
}
}
return false;
}
}
class Trie {
private Trie[] children;
private boolean isEnd;
public Trie() {
children=new Trie[26];
isEnd=false;
}
public void insert(String word) {
Trie node=this;
for(int i=0;i<word.length();i++){
char ch=word.charAt(i);
int index=ch-'a';
if(node.children[index]==null){
node.children[index]=new Trie();
}
node=node.children[index];
}
node.isEnd=true;
}
public Trie[] getChildren(){
return children;
}
public boolean isEnd(){
return isEnd;
}
}
72. 编辑距离(dp)
链接:https://leetcode-cn.com/problems/edit-distance/
dp[i][j] 代表 word1 到 i 位置转换成 word2 到 j 位置需要最少步数,所以,
当 word1[i] == word2[j],dp[i][j] = dp[i-1][j-1];
当 word1[i] != word2[j],dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
其中,dp[i-1][j-1] 表示替换操作,dp[i-1][j] 表示删除操作,dp[i][j-1] 表示插入操作。
分析及代码:
class Solution {
//dp
//dp[i][j] 代表 word1 到 i 位置转换成 word2 到 j 位置需要最少步数
public int minDistance(String word1, String word2) {
int L1=word1.length();
int L2=word2.length();
int[][] dp=new int[L1+1][L2+1];
//第一列
for(int i=1;i<=L1;i++) dp[i][0]=dp[i-1][0]+1;
//第一行
for(int j=1;j<=L2;j++) dp[0][j]=dp[0][j-1]+1;
for(int i=1;i<=L1;i++){
for(int j=1;j<=L2;j++){
//如果元素相同
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
}else{
dp[i][j]=Math.min(Math.min(dp[i-1][j-1],dp[i][j-1]),dp[i-1][j])+1;
}
}
}
return dp[L1][L2];
}
}
旋转数组
链接:https://leetcode-cn.com/problems/rotate-array/
class 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);//反转右边
}
//反转字符串
public void reverse(int[] nums,int start,int end){
while(start<end){
int temp=nums[start];
nums[start]=nums[end];
nums[end]=temp;
start++;
end--;
}
}
}