【刷题】Java 代码随想录 + java 数据结构

本文是作者刷题过程中的思考记录,主要涉及Java中的数据结构,包括数组和链表。文章讲解了数组的基础知识,如二分查找、移除元素、有序数组的平方等问题,强调了循环不变量原则的重要性。对于链表,文章讨论了链表的基本概念、单链表、双链表、循环链表,并分析了链表元素的添加和删除。此外,还提及了Java中数组和链表的区别以及排序算法的初步探讨。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


我是按照代码随想录的顺序刷的,github 地址,看到记得给个star

刷题顺序
数组-> 链表-> 哈希表->字符串->栈与队列->树->回溯->贪心->动态规划->图论->高级数据结构

数组

数组基础

  1. 数组是存放在连续内存空间上的相同类型数据的集合。
  2. 数组的元素是不能删的,只能覆盖。
  3. 在java中, 二维数组的每一行头结点的地址是没有规则的,更谈不上连续。

704. 二分查找

在这里插入图片描述

题解

/*
*给定一个n个元素有序的(升序)整型数组nums 和一个目标值target,写一个函数搜索nums中的
* target,如果目标值存在返回下标,否则返回 -1。
 */
public class Solution {
    public int search(int[] nums,int target){
        // 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
        if(target<nums[0]||target>nums[nums.length-1]){
            return -1;
        }  
        int left = 0;
        int right = nums.length-1;
        while(left<=right){
            int middle = left + (right-left)/2;  //  防止内存溢出 而不是使用int middle=(left+right)/2
            if(nums[middle]>target){
                right = middle;
            }else if(nums[middle]<target){
                left = middle;
            }else{
                return middle;
            }
        }
        return -1;
    }
}


想法

二分查找的条件

  1. 有序数组
  2. 无重复数据

二分查找需要注意的点

  1. 区间的划分 使用左闭右闭 所以right==left是有意义的
  2. 以后需要严格按照区间划分来写代码
  3. 区间的定义就是不变量,那么在循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则。

27. 移除元素

在这里插入图片描述

题解

class Solution {
    public int removeElement(int[] nums, int val) {
        int length = nums.length-1;
        int i=0; // 不等于val的元素数量
        int j=0; // 遍历元素
        while(j<=length){
            if(nums[j]!=val){   // 将不等于val从头到尾拿出来  
                nums[i++] = nums[j]; // 用j处的数据覆盖i处的数据 i++
            }
            j++;
        }
        return i;
    }
}

想法

1.数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。


977.有序数组的平方

题解

class Solution {
    public int[] sortedSquares(int[] nums) {

        int[] res = new int[nums.length];   // 定义必须初始化
        int i = 0;
        int j = 0;
        int length = nums.length-1;
        while(i<=length){
            res[j++] = nums[i]*nums[i];
            i++;
        }

        // 排序
        // 冒泡排序  从小到大
        for(int m =0;m<=length;++m){ // n次排序过程
            for(int n=1;n<=length-m;++n){  //  为什么这里是n<length-m  知道了 因为一次排序 最大的数必排到了最后面
                if(res[n-1]>res[n]){
                    int temp = res[n-1];
                    res[n-1] = res[n];
                    res[n] = temp;
                }
            }
        }      
        return res;
    }
}

用双指针法 速度快

class Solution {
    public int[] sortedSquares(int[] nums) {
        // 双指针法
        // 最大的数必在最右边或者最左边
        // 不同的是  只能交换  不能覆盖
        int length = nums.length;
        int[] res = new int[length];
        int left = 0;
        int right = length-1;
        int k = right;
        while(left<=right){
            if(nums[right]*nums[right]>=nums[left]*nums[left]){
                res[k--] = nums[right]*nums[right];
                right--;
            }else{
                res[k--] = nums[left]*nums[left];
                left++;
            }
        }
        

想法

java数组的三种初始化方法

  1. 静态初始化 初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组的长度;arrayName = new type[]{element1,element2,element3…}
  2. 简化的静态初始化方式: type[] arrayName = {element1,element2,element3…};
  3. 动态初始化:初始化时由程序员指定数组的长度,由系统初始化每个数组元素的默认值;type[] arrayName = new type[length];

209.长度最小的子数组

在这里插入图片描述

题解

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        int ans = Integer.MAX_VALUE;
        int start = 0, end = 0;
        int sum = 0;
        while (end < n) {
            sum += nums[end];
            while (sum >= s) {
                ans = Math.min(ans, end - start + 1);
                sum -= nums[start];
                start++;
            }
            end++;
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
}

想法

  1. 所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。

59. 螺旋矩阵 II

在这里插入图片描述

题解

class Solution {
    public int[][] generateMatrix(int n) {
        int number=1;
        int [][] res = new int[n][n]; 
        int up=0,left=0,down=n-1,right=n-1;
        while(up<down&&left<right){
            // 向右
        	for(int i=left;i<right;++i){
            	res[up][i] = number++;
        	}     

        	// 向下
        	for(int i=up;i<down;i++){
            	res[i][right] = number++;
       	 	}

        	//向左
       	 	for(int i=right;i>left;--i){
            	res[down][i] = number++;
        	}

        	// 向上
        	for(int i=down;i>up;--i){
            	res[i][left]=number++;
        	}

        	up++;
        	right--;
        	down--;
        	left++;
        	}
        // 加上最后一个数 以及等n=1时
        if(left==right){
            res[left][right] = number++;
        }
        return res;
    }
}

想法

  1. 二维数组的初始化
  2. int [][] a ={{1,2},{3,4}};
  3. int [][] ints= new int[3][3];
  4. int [][] ints = new int[5][]; 动态初始化

坚持循环不变原则!


矩阵总结

  • 数组是存放在连续内存空间上的相同类型数据的集合。
  • 数组可以方便的通过下标索引的方式获取到下标下对应的数据。
  • 因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。
    在这里插入图片描述
  • 数组的元素是不能删的,只能覆盖
  • 二维数据在内存中不是 3*4 的连续地址空间,而是四条连续的地址空间组成!
  • 循环不变量原则,只有在循环中坚持对区间的定义,才能清楚的把握循环中的各种细节
  • 双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作
  • 滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)



链表

链表基础

什么是链表,链表是一种通过指针串联在一起的线性结构,每一个节点是又两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链接的入口点称为列表的头结点也就是head。
在这里插入图片描述
单链表中的节点只能指向节点的下一个节点。

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

双链表 既可以向前查询也可以向后查询。
在这里插入图片描述
循环链表,顾名思义,就是链表首尾相连。

循环链表可以用来解决约瑟夫环问题。

在这里插入图片描述
链表是通过指针域的指针链接在内存中各个节点。
链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

定义链表

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;} 
}

数组与链表

在这里插入图片描述

数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。


0203.移除链表元素

在这里插入图片描述

题解

/**
 * 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; }
 * }
 */
public class Solution {
    public ListNode removeElements(ListNode head,int val){
        ListNode DummyHead = new ListNode(0);  // 创建一个虚拟头节点
        DummyHead.next = head;   // 将虚拟头节点指向head
        ListNode temp = DummyHead; // 再将temp指向Dummy 
        // 这时 DummyHead(temp)->head  
        // 所以 修改temp 也就是修改Dummy指向的数据  但是head指向的数据没有改变
        while(temp.next != null){
            if(temp.next.val==val){
                 temp.next = temp.next.next;  // temp 指向 下下节点
            }else{
                temp = temp.next;
            }
            
        }

        return DummyHead.next;
    }
}

链表的输入

void testReverseList() {
		System.out.println("testReverseList");
		ListNode head = new ListNode(1);//创建头节点
		head.next = new ListNode(2);//再定义头节点的next域
		ListNode t = head.next;  // 需要修改head 就写个t代替
		for(int i=3;i<10;i++) {//创建一个简单的链表{1,2,3,4,5,...,9}
			t.next = new ListNode(i);
			t = t.next;
		}
		
		ListNode newHead = ReverseList(head);//调用反转链表方法
		System.out.println(newHead.val);//检查新的头节点的值
		printListNode(newHead);//打印新链表的全部节点
	}
	//为了便于查看结果,写的打印链表的方法
	public void printListNode(ListNode head) {
		while(head!=null) {
			System.out.print(head.val+" ");
			head = head.next;
		}
	}

想法

  1. 以前想的比较少 下一节点 temp.next = temp.next.next
  2. 如果希望返回一个需要改动链表的头节点,那就定义一个ListNode temp = DummyHead,在temp上修改,返回Dummy

707. 设计链表

请添加图片描述

题解

   // 定义链表类
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 MyLinkedList {

    ListNode head;
    int size;

    /** Initialize your data structure here. */  
    // 构造函数
    public MyLinkedList() {
        head = new ListNode();   // 初始化一个链表头
        size=0;   // 链表的长度   初始化为0
    }
    
    /** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
    public int get(int index) {

        if(index>=size || index<0) return -1;

        ListNode temp = head;
        for(int i=0;i<=index;++i){
            temp = temp.next;
        }
        return temp.val;

    }
    
    /** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
    public void addAtHead(int val) {
        addAtIndex(0,val);
    }
    
    /** Append a node of value val to the last element of the linked list. */
    public void addAtTail(int val) {
        addAtIndex(size,val);
    }
    
    /** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
    public void addAtIndex(int index, int val) {
        if(index>size) return;
        if(index<0) index=0;


        size++;   // 增加

        // 找到前面一个节点  要插入的节点
        ListNode temp = head;

        for(int i=0;i<index;++i ){
            temp = temp.next;  // temp 作为指针域  一个地址 
        }

        // 将节点值为val的链表插入
        ListNode toAdd = new ListNode(val);
       toAdd.next = temp.next;
       temp.next = toAdd;


        
    }
    
    /** Delete the index-th node in the linked list, if the index is valid. */
    public void deleteAtIndex(int index) {
        // 删除一个节点
        // 在计算时  不能使用head  应该使用虚拟头结点
        if(index>=size || size<0) return ;
        size--;  
        ListNode temp = head;   // 使用一个temp节点代替
        // 先找到这个节点
        for(int i=0;i<index;++i){
            temp= temp.next;
        }
        temp.next = temp.next.next;

    }
}

其中插入链表的那一part

// 这样就可以
    toAdd.next = temp.next;   // 因为现在toAdd的指针域是空的  所以现在先需要存放地址
    // 将temp节点后接入的节点地址写入toAdd的后继指针域中 即将toAdd指向temp.next
 	temp.next = toAdd;  // 将toAdd的地址写入 temp的后继指针域 即temp指向toAdd
	
// 这样就不行
    temp.next=toAdd;  // 将toAdd的地址放入temp的后继指针域
    toAdd.next = temp.next;     // toAdd指向temp.next

// 等号左边和右边的区别 就是一个是指向 一个是添加 看next到底在左边还是右边

第一,当它在等号右边时(即X = L->next形式)一般是指将X指针指向头节点L的后一个节点也就是链表的第一个数据节点;(这个是指向节点)
第二,当它在等号左边的时候(即L->next = X),表示将X的值当做地址,写入到L的后继指针域L->next当中,相当于头节点L的后面链接一个数据节点。(这个是加了一个节点)

想法

参考链接链表指针

  1. 现在不是很了解 head.next = head 和 head = head.next 的区别和联系 它们分别代表什么意思
  2. 简单理解一下 head = head.next 是将head移到head.next的位置 而 head.next(这里换一个个比较好 比如temp.next) =head 即temp
  3. L->next有两层含义,可以表示L的后继节点指针,又可表示L的指针域,(补充:链表每个节点分为数据域和指针域,(单链表的指针域只有后继指针域,双链表的指针域分为前趋指针域和后继指针域))。第一,当它在等号右边时(即X = L->next形式)一般是指将X指针指向头节点L的后一个节点也就是链表的第一个数据节点;第二,当它在等号左边的时候(即L->next = X),表示将X的值当做地址,写入到L的后继指针域L->next当中,相当于头节点L的后面链接一个数据节点。基础概念理解后,对于p->next=s与p->next=s->next就好理解了,p->next=s:将指针变量s的值写入p的后继指针域,相当于p数据节点后又链接一个指针s指向数据节点,p->next=s->next:将s节点后接的节点的地址写入到p节点的后继指针域,相当于p节点插入到原先s节点与s的后接节点中间。



Java基础知识

排序算法

直接插入排序算法

基本思想:将待排序的序列从头到尾遍历,将其分为前端有序序列和后端无序序列,具体做法,取一段序列a[n],假设前端(0,1,…,end)是有序序列,那么只需要将a[end+1]从后到前,依次与a[end],a[end-1],…a[0]比较,将其插入以保证a[0],a[1],a[2],…,a[end],a[end+1]有序序列。

public class Solution {
    void InsertSort(int []a,int n){
        // 输入 a 待排序数组
        // n 数组长度
        for(int i=0;i<n-1;++i){
            int end = i;
            int temp = a[end+1];  // 无序序列的第一个 需要将其跟前一个进行比较
            while (end>=0){ // 需要将a[end+1]与序列比较 直到找到比它小的数 或者end<0
                if(a[end]>temp){
                    a[end+1] = a[end];
                    end--;
                }else{
                    break;
                }
            }
            a[end+1] = temp;
        }
    }
}

希尔排序算法

希尔排序算法又称缩小增量法,基本含义是将待排序序列以一个指定gap分割成几个组,对每个组进行排序,缩小gap重复以上步骤,当gap为1时,数组就排序完成(gap为1时,算法就是直接插入排序,整个算法其实也就是对数组进行预排序,减少最后直接插入排序的时间复杂度)

public class Solution {
    void xiErSort(int[] a,int n){
        /* 输入
        a 待排序数组
        n 长度
        * */
        int gap = n;   // gap 分组间隔
        while(gap>1){
            gap = gap / 2;
            for(int i=0;i< n - gap;++i){  // 不用到n
                int end = i;
                int temp = a[end+gap];
                while (end>=0){
                    if(a[end]>temp){
                        a[end+gap] = a[end];
                        end -= gap;
                    }else{
                        break;
                    }
                }
                a[end+gap] = temp;
            }
        }
    }
}

冒泡排序

n次排序,每次最大的数都跑到最后面

// 冒泡排序  从小到大
for(int m =0;m<=length;++m){ // n次排序过程
	for(int n=1;n<=length;++n){  //  为什么这里是n<length-m  知道了 因为一次排序 最大的数必排到了最后面
		if(res[n-1]>res[n]){
			int temp = res[n-1];
            res[n-1] = res[n];
            res[n] = temp;
         }
    }
 }

冒泡排序优化 加个flag,检测排序是否提前结束

boolean flag = true;
        while(flag){
            flag = false;  // 假设不经过排序
            for(int n=1;n<=length;++n){  
                if(res[n-1]>res[n]){
                    int temp = res[n-1];
                    res[n-1] = res[n];
                    res[n] = temp;
                }
                flag = true;
            }
            length--; // 一次排序 最大的数必排到了最后面 就不必再排最后一个数
        }

i++和++i

直接上代码

public class Test{
    public static void main(String[] args) {
        // 测试++i和i++
        int[] test={0,1,2,3,4,5,6};
        int i=0;
        int j=0;
        System.out.println(test[i++]); // 先计算test[i] 再i+=1;
        System.out.println(i);
		// 结果: 0  1 
		System.out.println(test[++j]);// 先i+=1,再计算test[i];
        System.out.println(j);
        // 结果: 1  1
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值