链表翻转、插入、删除
反转
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null||head.next==null){
return head;
}
ListNode cur=head;
ListNode reversehead=null;
ListNode temp=null;
while(cur!=null){
temp=cur.next;
cur.next=reversehead;
reversehead=cur;
cur=temp;
}
return reversehead;
}
}
删除链表节点
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode dummyhead=new ListNode(0);//采用了虚拟头节点
dummyhead.next=head;
ListNode cur=head;
ListNode pre=dummyhead;
while(cur!=null){
if(cur.val==val){
pre.next=cur.next;
break;
}
pre=cur;
cur=cur.next;
}
return dummyhead.next;
}
}
链表中倒数第K个结点
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
if(k==0||head==null){
return null;
}
ListNode first=head;
ListNode second=head;
for(int i=0;i<k-1;i++){
second=second.next;//比如要找倒数第二个,则先划定范围在两个
if(second==null){
return null;
}
}
while(second.next!=null){//然后再往后移动,直到second为最后一个
first=first.next;
second=second.next;
}
return first;
}
}
合并排序链表
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1== null && l2 == null) { //如果两个链表都为空
return null;
}
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
ListNode head;//新链表的头节点
ListNode cur;//用来比较的中间节点
//初始化,将head和cur都指向两个链表中最小的节点
if(l1.val<l2.val){
head=l1;
cur=l1;
l1=l1.next;
}
else{
head=l2;
cur=l2;
l2=l2.next;
}
//比较,小的加入链表,直到其中一条链表为空
while(l1!=null&&l2!=null){
if(l1.val<l2.val){
cur.next=l1;
cur=cur.next;
l1=l1.next;
}
else{
cur.next=l2;
cur=cur.next;
l2=l2.next;
}
}
while(l1!=null){
cur.next=l1;
cur=cur.next;
l1=l1.next;
}
while(l2!=null){
cur.next=l2;
cur=cur.next;
l2=l2.next;
}
return head;
}
}
从尾到头打印链表
public class Solution {
public int[] reversePrint(ListNode head) {
ArrayList<Integer> list = new ArrayList<>();
while (head != null) {
list.add(head.val);
head = head.next;
}
int size = list.size();
int[] array = new int[size];
for (int i = 0; i < size; i++) {
array[i] = list.get(size - i -1);
}
return array;
}
}
回文链表
输入: 1->2->2->1
输出: true
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode reversed=reverseAndClone(head);
return isEqual(head,reversed);
}
public ListNode reverseAndClone(ListNode node){
ListNode head = null;
while(node != null) {
ListNode n = new ListNode(node.val); //复制
n.next = head;
head = n;
node = node.next;
}
return head;
}
public boolean isEqual(ListNode node1,ListNode node2){
while(node1!=null&&node2!=null){
if(node1.val!=node2.val){
return false;
}
node1=node1.next;
node2=node2.next;
}
return node1==null&&node2==null;
}
}
K个一组翻转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
public ListNode reverseKGroup(ListNode head, int k) {
if (head == null || head.next == null) {
return head;
}
ListNode tail = head;
for (int i = 0; i < k; i++) {
//剩余数量小于k的话,则不需要反转。
if (tail == null) {
return head;
}
tail = tail.next;
}
// 反转前 k 个元素
ListNode newHead = reverse(head, tail);
//下一轮的开始的地方就是tail
head.next = reverseKGroup(tail, k);
return newHead;
}
/*
左闭右开区间
*/
private ListNode reverse(ListNode head, ListNode tail) {
ListNode pre = null;
ListNode next = null;
while (head != tail) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
在 O(n log n) 时间复杂度和常数级的空间复杂度下给链表排序
归并排序
public ListNode sortList(ListNode head) {
// 0个节点 || 1个节点
if (head == null || head.next == null)
return head;
// >= 2个节点
ListNode first = head, second = null, mid = getMid(head);
second = mid.next;
mid.next = null; //将链表分为两段!!!!
//递归
first = sortList(first);
second = sortList(second);
return merge(first, second);
}
//排序
ListNode merge(ListNode first, ListNode second) {
if (first == null)
return second;
if (second == null)
return first;
ListNode res = new ListNode(0);
ListNode curr = res;//控制新链表顺序的point
while (first != null && second != null) {
if (first.val < second.val) {
curr.next = first;
curr = curr.next;
first = first.next;
} else {
curr.next = second;
curr = curr.next;
second = second.next;
}
}
if (first != null)
curr.next = first;
if (second != null)
curr.next = second;
return res.next;
}
//将链表平分为两段,返回第一段末尾 例如:5个点返回2号点,6个点返回3号点
ListNode getMid(ListNode head) {
ListNode slow = head, fast = head.next;
while (fast!=null&&fast.next!=null) {
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
字符串
在给定字符串情况下,求最长的不含重复字符的字串的长度。
class Solution {
public int lengthOfLongestSubstring(String s) {
int head=0;
int tail=0;
if(s.length()<2){
return s.length();
}
int res=1;
while(tail<s.length()-1){
tail++;
if(!s.substring(head,tail).contains(s.substring(tail,tail+1))){//如果当前元素之前的不重复结果集没包含该元素,则包含进来
res=Math.max(tail-head+1,res);
}
else{
while(s.substring(head,tail).contains(s.substring(tail,tail+1))){//包含该元素,头节点往后移
head++;
}
}
}
return res;
}
}
最长公共子序列
一,定要明确 dp 数组的含义
对于两个字符串的动态规划问题,套路是通用的
比如说对于字符串 s1 和 s2,它们的长度分别是 m、n,一般来说都要构造一个这样的 DP table:int[][] dp = new int[m+1][n+1]。
这里为什么要加1,原因是你可以不加1,但是不加1你就会用其它限制条件来确保这个index是有效的,而当你加1之后你就不需要去判断只是让索引为0的行和列表示空串。
第二步,定义 base case
我们专门让索引为0的行和列表示空串,dp[0][…] 和 dp[…][0] 都应该初始化为0,这就是base case。
第三步,找状态转移方程
text1 = “abcde”, text2 = “ace”
最长公共子序列是 “ace”,它的长度为 3。
第一次遍历 i = 1, j = 1,两个a相同所以 dp[1][1] = 1
第二次遍历 i = 1, j = 2,a与c不等,也不能是0,这里需转换成 a 与 ac 最长子序列,这里需要把之前的关系传递过来,所以dp[1][2] = 1
第三次遍历 i = 1, j = 3,a与e不相同,把之前的关系传递过来,所以dp[1][3] = 1
因此可以得出:
现在对比的这两个字符不相同的,那么我们要取它的「要么是text1往前退一格,要么是text2往前退一格,两个的最大值」
dp[i + 1][j + 1] = Math.max(dp[i+1][j], dp[i][j+1]);
对比的两个字符相同,去找它们前面各退一格的值加1即可:dp[i+1][j+1] = dp[i][j] + 1;
// java
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 获取两个串字符
char c1 = text1.charAt(i), c2 = text2.charAt(j);
if (c1 == c2) {
// 去找它们前面各退一格的值加1即可
dp[i + 1][j + 1] = dp[i][j] + 1;
} else {
//要么是text1往前退一格,要么是text2往前退一格,两个的最大值
dp[i + 1][j + 1] = Math.max(dp[i + 1][j], dp[i][j + 1]);
}
}
}
return dp[m][n];
}
}
编辑距离:
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)
.1. dp[i][j] 代表 word1 中前 i 个字符,变换到 word2 中前 j 个字符,最短需要操作的次数
2.需要考虑 word1 或 word2 一个字母都没有,即全增加/删除的情况,所以预留 dp[0][j] 和 dp[i][0]
3.状态转移
31. 增,dp[i][j] = dp[i][j - 1] + 1
32. 删,dp[i][j] = dp[i - 1][j] + 1
33. 改,dp[i][j] = dp[i - 1][j - 1] + 1
3.4按顺序计算,当计算 dp[i][j] 时,dp[i - 1][j] , dp[i][j - 1] , dp[i - 1][j - 1] 均已经确定了
3.5配合增删改这三种操作,需要对应的 dp 把操作次数加一,取三种的最小
3.6如果刚好这两个字母相同 word1[i - 1] = word2[j - 1] ,那么可以直接参考 dp[i - 1][j - 1] ,操作不用加一
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length();
int len2 = word2.length();
int[][] dp = new int[len1 + 1][len2 + 1];
// 插入操作
for(int i = 1; i <= len1; i++) {
dp[i][0] = dp[i - 1][0] + 1;
}
// 删除操作
for(int j = 1;j <= len2; j++) {
dp[0][j] = dp[0][j - 1] + 1;
}
for(int i = 1; i <= len1; i++) {
for(int j = 1; j <= len2; j++) {
// 删除操作:dp[i - 1][j]
// 增加操作:dp[i][j - 1]
// 替换操作:dp[i - 1][j - 1]
dp[i][j] = Math.min(dp[i - 1][j], Math.min(dp[i - 1][j - 1], dp[i][j - 1])) + 1;//取三种最小
if(word1.charAt(i - 1) == word2.charAt(j - 1)) {//表示i,j位置字母相同
dp[i][j] =dp[i - 1][j - 1];
}
}
}
return dp[len1][len2];
}
}
二叉树
1.二叉树前序遍历(递归和非递归)
递归:
public void qianxu(node t)// 前序递归 前序遍历:根结点 ---> 左子树 ---> 右子树
{
if (t != null) {
System.out.print(t.value + " ");// 当前节点
qianxu(t.left);
qianxu(t.right);
}
}
非递归:
每pop完添加右左节点直接输出(访问)即可完成前序非递归遍历。
public void qianxu3(node t)// 非递归前序 栈 先左后右 t一般为root
{
Stack<node> q1 = new Stack<node>();
if (t == null)
return;
if (t != null) {
q1.push(t);
}
while (!q1.empty()) {
node t1 = q1.pop();
if (t1.right != null) {
q1.push(t1.right);
}
if (t1.left != null) {
q1.push(t1.left);
}
System.out.print(t1.value + " ");
}
}
2.二叉树中序遍历(递归和非递归)
递归:
public void zhongxu(node t)// 中序遍历 中序遍历:左子树---> 根结点 ---> 右子树
{
if (t != null) {
zhongxu(t.left);
System.out.print(t.value + " ");// 访问完左节点访问当前节点
zhongxu(t.right);
}
}
非递归:
public void zhongxu2(node t) {
Stack<node> q1 = new Stack();
while(!q1.isEmpty()||t!=null)
{
if (t!=null) {
q1.push(t);
t=t.left;
}
else {
t=q1.pop();
System.out.print(t.value+" ");
t=t.right;
}
}
}
3.二叉树后序遍历(递归和非递归)
递归:
public void houxu(node t)// 后序遍历 后序遍历:左子树 ---> 右子树 ---> 根结点
{
if (t != null) {
houxu(t.left);
houxu(t.right);
System.out.print(t.value + " "); // 访问玩左右访问当前节点
}
}
非递归:
public void houxu2(node t) {
Stack<node> q1 = new Stack();
Map<Integer,Integer >map=new HashMap<>();
while(!q1.isEmpty()||t!=null)
{
if (t!=null) {
q1.push(t);
map.put(t.value, 1); //t.value标记这个值节点出现的次数
t=t.left;
}
else {
t=q1.peek();
if(map.get(t.value)==2) {//第二次访问,抛出
q1.pop();
System.out.print(t.value+" ");
t=null;//需要往上走
}
else {
map.put(t.value, 2);
t=t.right;
}
}
}
}
4.二叉树查找的最短搜索路径
public class TreeNode {
int val=0;
TreeNode left=null;
TreeNode right=null;
public TreeNode(int val) {
this.val = val;
}
}
public class Solution2 {
private ArrayList<ArrayList<Integer>> allPaths=new ArrayList<ArrayList<Integer>>();
private ArrayList<Integer> onePath=new ArrayList<Integer>();
public ArrayList<ArrayList<Integer>> findAllPath(TreeNode root) {
if(root==null)
return allPaths;
onePath.add(root.val);
//若为叶子节点,则onePath加入到allPaths;
if(root.left==null&&root.right==null){
allPaths.add(new ArrayList<Integer>(onePath));
}
findAllPath(root.left);
findAllPath(root.right);
onePath.remove(onePath.size()-1);
return allPaths;
}
}
数组
1.积水问题
public static void main(String[] args) {
int [] a={0,1,0,2,1,0,1,3,2,1,2,1};
int b=getwater2(a);
System.out.print(b);
}
private static int getwater2(int[] orgin){
int size=orgin.length;
if(size<3){
return 0;
}
System.out.println(Arrays.toString(orgin));
int[] water= new int[size];
int maxNum=0;
for(int i=0;i<size;i++){//第一次遍历得到最高台阶高度
if(orgin[i]>maxNum){
maxNum=orgin[i];
}
}
for(int i=0;i<size;i++){//初始化积水统计数组
water[i]=maxNum-orgin[i];
}
int left=orgin[0];
if(water[0]>0){//不是开头最高
water[0]=0;
for(int j=1;j<size&&orgin[j]<maxNum;j++){
if(orgin[j]>=left){//大于左边元素,left指针移到该元素位置,并让该位置的water数组值为0
left=orgin[j];
water[j]=0;
}else{//否则让该位置water元素值为left-orgin[j]
water[j]=left-orgin[j];
}
}
}
int right=orgin[size-1];
if(water[size-1]>0){//不是结尾最高
water[size-1]=0;
for(int j=(size-2);j<size&&orgin[j]<maxNum;j--){
if(orgin[j]>=right){
right=orgin[j];
water[j]=0;
}else{
water[j]=right-orgin[j];
}
}
}
int sum=0;
for(int i=0;i<size;i++){
sum+=water[i];
}
return sum;
}
2.两个排序的数组A和B分别含有m和n个数,找到两个排序数组的中位数,要求时间复杂度应为O(log (m+n))。
给出数组A = [1,2,3,4,5,6] B = [2,3,4,5],中位数3.5
给出数组A = [1,2,3] B = [4,5],中位数 3
class Solution {
/**
* @param A: An integer array.
* @param B: An integer array.
* @return: a double whose format is *.5 or *.0
*/
public double findMedianSortedArrays(int[] A, int[] B) {
if (A == null && B == null) return 0;
int len = A.length + B.length;
if (len % 2 == 0){
return (findKth(A, 0, B, 0, len/2) + findKth(A, 0, B, 0, len/2 + 1)) / 2.0;
}
return findKth(A, 0, B, 0, len/2+1);
}
public static double findKth(int[] A, int startA, int[] B, int startB, int k){
if (startA >= A.length){
return B[startB + k - 1];
}
if (startB >= B.length){
return A[startA + k - 1];
}
if (k == 1){
return Math.min(A[startA], B[startB]);
}
int keyA = startA + k / 2 - 1 < A.length ? A[startA + k / 2 - 1] : Integer.MAX_VALUE;
int keyB = startB + k / 2 - 1 < B.length ? B[startB + k / 2 - 1] : Integer.MAX_VALUE;
if (keyA < keyB){
return findKth(A, startA + k / 2, B, startB, k - k / 2);
}
return findKth(A, startA, B, startB + k / 2, k - k / 2);
}
}
堆
1.数组中第k大的数-最小堆
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2 输出: 5
public int findKthLargest(int[] nums, int k) {
final PriorityQueue<Integer> queue = new PriorityQueue<>();//PriorityQueue默认是最小堆,即孩子节点比父亲节点大,则堆顶是最小的,所以我们维护一个容量大小为K的堆,当大小超过K时,则删除堆顶元素,最后的堆顶元素就是第K大的元素了
for (int val : nums) {
queue.add(val);
if (queue.size() > k)
queue.poll();
}
return queue.peek();
}
时间复杂度为O(N*logk),因为二叉堆的插入和删除操作都是logk的时间复杂度。上述代码是“不处理重复数据方法”.