链表
定义
单向链表
public class ListNode {
// 结点的值
int val;
// 下一个结点
ListNode next;
// 节点的构造函数(无参)
public ListNode() {
}
// 节点的构造函数(有一个参数)
public ListNode(int val) {
this.val = val;
}
// 节点的构造函数(有两个参数)
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
双向链表
class ListNode {
}
移除链表元素
设置pre(初始指向虚拟头结点)和cur(初始指向头结点)两个指针,记得先判断head为不为空
public ListNode removeElement(ListNode head,int val){
if(head == null){
return null}
ListNode dummy= new ListNode(-1,head);
ListNode pre=dummy;
ListNode cur=head;
while(cur !=null){
if(cur.val ==val){
pre.next=cur.next;}else{
pre=cur;}
cur=cur.next;}
return dummy.next;}
链表基本操作
//单链表
//定义节点
class ListNode {
int val;
ListNode next;
ListNode(){}
ListNode(int val) {
this.val=val;
}
}
//定义链表
class MyLinkedList {
//size存储链表元素的个数
int size;
//虚拟头结点
ListNode head;
//初始化链表
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
//获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
public int get(int index) {
//如果index非法,返回-1
if (index < 0 || index >= size) {
return -1;
}
ListNode currentNode = head;
//包含一个虚拟头节点,所以查找第 index+1 个节点
for (int i = 0; i <= index; i++) {
currentNode = currentNode.next;
}
return currentNode.val;
}
//在链表最前面插入一个节点,等价于在第0个元素前添加
public void addAtHead(int val) {
addAtIndex(0, val);
}
//在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
public void addAtTail(int val) {
addAtIndex(size, val);
}
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
if (index > size) {
return;
}
if (index < 0) {
index = 0;
}
size++;
//找到要插入节点的前驱
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
ListNode toAdd = new ListNode(val);
toAdd.next = pred.next;
pred.next = toAdd;
}
//删除第index个节点
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
size--;
if (index == 0) {
head = head.next;
return;
}
ListNode pred = head;
for (int i = 0; i < index ; i++) {
pred = pred.next;
}
pred.next = pred.next.next;
}
}
//双链表
class ListNode{
int val;
ListNode next,prev;
ListNode() {};
ListNode(int val){
this.val = val;
}
}
class MyLinkedList {
//记录链表中元素的数量
int size;
//记录链表的虚拟头结点和尾结点
ListNode head,tail;
public MyLinkedList() {
//初始化操作
this.size = 0;
this.head = new ListNode(0);
this.tail = new ListNode(0);
//这一步非常关键,否则在加入头结点的操作中会出现null.next的错误!!!
head.next=tail;
tail.prev=head;
}
public int get(int index) {
//判断index是否有效
if(index<0 || index>=size){
return -1;
}
ListNode cur = this.head;
//判断是哪一边遍历时间更短
if(index >= size / 2){
//tail开始
cur = tail;
for(int i=0; i< size-index; i++){
cur = cur.prev;
}
}else{
for(int i=0; i<= index; i++){
cur = cur.next;
}
}
return cur.val;
}
public void addAtHead(int val) {
//等价于在第0个元素前添加
addAtIndex(0,val);
}
public void addAtTail(int val) {
//等价于在最后一个元素(null)前添加
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
//index大于链表长度
if(index>size){
return;
}
//index小于0
if(index<0){
index = 0;
}
size++;
//找到前驱
ListNode pre = this.head;
for(int i=0; i<index; i++){
pre = pre.next;
}
//新建结点
ListNode newNode = new ListNode(val);
newNode.next = pre.next;
pre.next.prev = newNode;
newNode.prev = pre;
pre.next = newNode;
}
public void deleteAtIndex(int index) {
//判断索引是否有效
if(index<0 || index>=size){
return;
}
//删除操作
size--;
ListNode pre = this.head;
for(int i=0; i<index; i++){
pre = pre.next;
}
pre.next.next.prev = pre;
pre.next = pre.next.next;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
反转链表
双指针法
public ListNode reverseList(ListNode head){
ListNode prev=null;
ListNode cur=head;
ListNode temp=null;
while(cur!=null){
temp=cur.next;
cur.next=prev;
prev=cur;
cur=temp;
}
return prev;
}
交换链表中相邻的两个结点
//递归版本
class solution{
public ListNode swapPairs(ListNode head){
if(head==null||head.next==null) return head;
ListNode next=head.next;
ListNode newNode=swapPairs(next.next);
next.next=head;
head.next=newNode;
return next;
}
}
//普通版本,需要3个指针来指向交换的两个节点和他们前面的节点
class solution{
public ListNode swappairs(ListNode head){
ListNode dumyhead=new ListNode(-1);
dumyhead.next=head;
ListNode cur=dumyhead;
ListNode firtnode;//两个节点中的第一个节点
ListNode secondnode;//两个节点中的第二个节点
while(cur.next!=null && cur.next.next!=null){
firstnode=cur.next;
secondnode=cur.next.next;
cur.next=secondnode;
firstnode.next=secondnode.next;
secondenode.next=firstnode;
cur=firstnode;
}
return dumyhead.next;
}
}
删除倒数第n个节点
//使用两个指针,slow和往前走了n步的fast指针,初始两指针都指向虚拟头结点
class solution{
public ListNode deleteNode(ListNode head,int n){
ListNode dummyNode=new ListNode(0);
dummyNode.next=head;
ListNode fastNode=dummyNode;
ListNode slowNode=dummyNode;
for(int i=0;i<0;i++){
fastNode=fastNode.next;
}
while(fastNode!=null){
slowNode=slowNode.next;
fastNode=fastNode.next;
}
if(slowNode.next!=null){
slowNode.next=slowNode.next.next;
}
return dummyNode.next;
}
}
链表相交
面试题 02.07. 链表相交 - 力扣(LeetCode)
//双指针法,同步移动链表实现相遇
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;
}
}
//先移动长链表实现移动
class solution{
public ListNode getIntersectionNode(ListNode headA,ListNode headB){
ListNode curA=headA;
ListNode curB=headB;
int lenA=0,lenB=0;
while(curA!==null){
lenA++;
curA=curA.next;
}
while(curB!=null){
lenB++;
curB=curB.next;
}
curA=headA;
curB=headB;
//保证A指向的为最长链
if(lenB>lenA){
int tempLen=lenA;
lenA=lenB;
lenB=tempLen;
ListNode tempNode=curA;
curA=curB;
curB=tempNode;
}
//求两条链子的长度差
int gap=lenA-lenB;
while(gap-->0){//表示2个操作,gap--后和0做比较
curA=curA.next;
}
while (curA != null) {//一定要记得判断curA是否为空
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
循环链表
题意: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
//使用快慢指针,通过是否相遇判断是否存在环;相遇之后再分别从头结点和相遇节点往前,两指针的相遇点就是环的起点
class solution{
public ListNode detectCycle(ListNode head){
ListNode slow=head;
ListNode fast=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(slow==fast){
ListNode index1=fast;
ListNode index2=head;
while(index1!=index2){
index1=index1.next;
index2=index2.next;
}
return index1;
}
}
return null;
}
}
数组
像Java是没有指针的,同时也不对程序员暴露其元素的地址,寻址操作完全交给虚拟机。所以看不到每个元素的地址情况
二分查找
left=0;
right=size-1;
middle=(right+left)/2;
//左闭右闭区间[left,right]
while(left<=right){
if(target<middle) right=middle-1;
else if(target>middle) left=middle+1;
else return middle;}
return -1;
//左闭右开区间[left,right)
while(left<right){
if(target<middle) right=middle;
else if(target>middle) left=middle+1;
else return middle;}
return -1;
移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。
示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
//同向双指针解法,快指针查找所有元素,然后符合条件的就将值更新给慢指针
class solution{
public int removeElement(int[] nums,int val){
int slow=0;
for(int fast=0;fast<nums.length;fast++){
if(fast!=val){
nums[slow++]=nums[fast];
}
}
return slow;
}
}
有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
- 输入:nums = [-4,-1,0,3,10]
- 输出:[0,1,9,16,100]
- 解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100]
示例 2:
- 输入:nums = [-7,-3,2,3,11]
- 输出:[4,9,9,49,121]
解法:使用首尾双指针从两端指向中间
class Solution {
public int[] sortedSquares(int[] nums) {
int right = nums.length - 1;
int left = 0;
int[] result = new int[nums.length];
int index = result.length - 1;
while (left <= right) {
if (nums[left] * nums[left] > nums[right] * nums[right]) {
// 正数的相对位置是不变的, 需要调整的是负数平方后的相对位置
result[index--] = nums[left] * nums[left];
++left;
} else {
result[index--] = nums[right] * nums[right];
--right;
}
}
return result;
}
}
长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
- 输入:s = 7, nums = [2,3,1,2,4,3]
- 输出:2
- 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
解法:用一个for循环来依次移动滑动窗口尾部的指针,然后在里面移动滑动窗口头部的指针看是否满足条件
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int result=Integer.MAX_VALUE;
int left=0;
int sum=0;
for (int right=0;right<nums.length;right++){
sum+=nums[right];
while(sum>=target){
result=Math.min(result,right-left+1);
sum-=nums[left++];
}
}
return result==Integer.MAX_VALUE?0:result;
}
}
螺旋矩阵II
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
解法:每圈为一个循环(因为绕法相同),然后每圈确定以左上角的点为起始XY(最开始是(0,0),之后每次递增1),确定每个边的边长,确定循环结束的条件(起始点转到了中间的位置,即X<=n/2时可以继续循环)
注意:当n为奇数时还要单独处理中间的值
class Solution {
public int[][] generateMatrix(int n) {
int[][] nums = new int[n][n];
int startX = 0, startY = 0; // 每一圈的起始点
int offset = 1;
int count = 1; // 矩阵中需要填写的数字
int loop = 1; // 记录当前的圈数
int i, j; // j 代表列, i 代表行;
while (loop <= n / 2) {
// 顶部
// 左闭右开,所以判断循环结束时, j 不能等于 n - offset
for (j = startY; j < n - offset; j++) {
nums[startX][j] = count++;
}
// 右列
// 左闭右开,所以判断循环结束时, i 不能等于 n - offset
for (i = startX; i < n - offset; i++) {
nums[i][j] = count++;
}
// 底部
// 左闭右开,所以判断循环结束时, j != startY
for (; j > startY; j--) {
nums[i][j] = count++;
}
// 左列
// 左闭右开,所以判断循环结束时, i != startX
for (; i > startX; i--) {
nums[i][j] = count++;
}
startX++;
startY++;
offset++;
loop++;
}
if (n % 2 == 1) { // n 为奇数时,单独处理矩阵中心的值
nums[startX][startY] = count;
}
return nums;
}
}
区间和(利用前缀和思想求解)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] vec = new int[n];
int[] p = new int[n];
int presum = 0;
for (int i = 0; i < n; i++) {
vec[i] = scanner.nextInt();
presum += vec[i];
p[i] = presum;
}
while (scanner.hasNextInt()) {
int a = scanner.nextInt();
int b = scanner.nextInt();
int sum;
if (a == 0) {
sum = p[b];
} else {
sum = p[b] - p[a - 1];
}
System.out.println(sum);
}
scanner.close();
}
}
开发商购买土地
44. 开发商购买土地(第五期模拟笔试) (kamacoder.com)
题目:输入n*m的矩阵,使得通过横向或者纵向一刀切之后,可以分别分给A和B两个公司,使得两个公司的数字和所得差值最小,并输入此时的差值
暴力解法:使用前缀和的方法来计算行列之间的差值
优化解法:在遍历的时候,遇到行末尾就 计算一次求和,遇到列末尾就计算一次求和
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt();
int m=scanner.nextInt();
int[][] vec=new int[n][m];
int sum=0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
vec[i][j]=scanner.nextInt();
sum+=vec[i][j];
}
}
int result=Integer.MAX_VALUE;
int count=0;//统计遍历过的行
//行切分
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
count+=vec[i][j];
}
result=Math.min(result,Math.abs(sum-count-count));
}
//列切分
count=0;
for(int j=0;j<m;j++){
for(int i=0;i<n;i++){
count += vec[i][j];
}
result=Math.min(result,Math.abs(sum-2*count));
}
System.out.println(result);
scanner.close();
}
}
哈希表/散列表(Hash Table)
使用场景:快速判断一个元素是否存在于集合中,根据关键码的值直接进行访问,使用空间换时间,只需要O(1)的时间复杂度
定义
哈希函数
1.将索引进行编码,2.通过对编码取模放在相应的位置
哈希碰撞
拉链法
选择合适的链表大小,不会因为数组空值而浪费内存,也不会因为链表太长而浪费查找时间
线性探测法
tableSize一定要大于dataSize
常见哈希结构
数组:可能的数值量小的时候可以用数组
set
map
有效的异位词
题目:给定两个字符串 s
和 t
,编写一个函数来判断 t
是否是 s
的字母异位词。
注意:若 s
和 t
中每个字符出现的次数都相同,则称 s
和 t
互为字母异位词。
解法:数量较少,使用数组即可以解决
两数组交集
题目:给定两个数组 nums1
和 nums2
,返回 它们的交集。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
字符串
反转字符串
题目:
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1:
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
解法:
使用双指针分别指向头尾,之后再交换,然后两指针同步往中间移动,直到最终移动到中间(或者两指针相遇)为止
注意:尾指针的初始坐标记得用长度减一
class Solution {
public void reverseString(char[] s) {
int n=s.length;
int r=n-1;
for(int l=0;l<n/2;l++){
char temp=s[l];
s[l]=s[r];
s[r]=temp;
r--;
}
}
}
反转字符串II
题目:
给定一个字符串 s 和一个整数 k,从字符串开头算起, 每计数至 2k 个字符,就反转这 2k 个字符中的前 k 个字符。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
解法:
进行循环,循环每次跳的步长为2k,在每个循环里判断字符串长度是否超过i,超过则reverse(0,i),没超过则全部reverse.每个循环中翻转的部分的开头都使用start指针(start为i),结尾都使用end指针(end为start+k-1和length-1中取最小值)
注:
字符串转换为数组:string.toCharArray()
数组转换为字符串:new String(ch)
class Solution {
public String reverseStr(String s, int k) {
char[] ch=s.toCharArray();
for(int i=0;i<ch.length;i+=2*k){
int start=i;
int end=Math.min(ch.length-1,start+k-1);
while(start<end){
char temp=ch[start];
ch[start]=ch[end];
ch[end]=temp;
start++;
end--;
}
}
return new String(ch);
}
}
替换数字
54. 替换数字(第八期模拟笔试) (kamacoder.com)
题目:
给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。
例如,对于输入字符串 "a1b2c3",函数应该将其转换为 "anumberbnumbercnumber"。
解法:
双指针解法,先找到旧字符串和新字符串的结尾位置,然后倒着向前,慢指针遇到数字时快指针更新“number”,直到两指针都指向最开头
注意:
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素整体向后移动。
字符串按下标取值方法为S.charAt(i);数组按下标取值方法为nums[i]
//还没看出来错在哪儿了
import java.util.Scanner;
public class Main{
public static String replaceNumber(String s){
int count=0;//统计数字个数
int oldSize=s.length();
for(int i=0;i<s.length();i++){
if(Character.isDigit(s.charAt(i))){
count++;
}
}
int newSSize=oldSize+count*5;
char[] newS=new char[newSSize];
for(int i=newSSize-1,j=oldSize-1;j>=0;j--){
if(!Character.isDigit(s.charAt(j))){
newS[i]=s.charAt(j);
}else{
newS[i--]='r';
newS[i--]='e';
newS[i--]='b';
newS[i--]='m';
newS[i--]='u';
newS[i--]='n';
}
}
return new String(newS);
}
public static void main(String[] args){
Scanner scanner=new Scanner(System.in);
String s=scanner.next();
System.out.println(replaceNumber(s));
scanner.close();
}
}
翻转单词
题目:
给定一个字符串,逐个翻转字符串中的每个单词。输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
解法:
1.移除多余空格(使用快慢指针移除,慢指针逐个往前,快指针找适合的元素)
2.将整个字符串反转
3.将每个单词反转
class Solution {
/**
* 不使用Java内置方法实现
* <p>
* 1.去除首尾以及中间多余空格
* 2.反转整个字符串
* 3.反转各个单词
*/
public String reverseWords(String s) {
// System.out.println("ReverseWords.reverseWords2() called with: s = [" + s + "]");
// 1.去除首尾以及中间多余空格
StringBuilder sb = removeSpace(s);
// 2.反转整个字符串
reverseString(sb, 0, sb.length() - 1);
// 3.反转各个单词
reverseEachWord(sb);
return sb.toString();
}
private StringBuilder removeSpace(String s) {
// System.out.println("ReverseWords.removeSpace() called with: s = [" + s + "]");
int start = 0;
int end = s.length() - 1;
while (s.charAt(start) == ' ') start++;
while (s.charAt(end) == ' ') end--;
StringBuilder sb = new StringBuilder();
while (start <= end) {
char c = s.charAt(start);
if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
sb.append(c);
}
start++;
}
// System.out.println("ReverseWords.removeSpace returned: sb = [" + sb + "]");
return sb;
}
/**
* 反转字符串指定区间[start, end]的字符
*/
public void reverseString(StringBuilder sb, int start, int end) {
// System.out.println("ReverseWords.reverseString() called with: sb = [" + sb + "], start = [" + start + "], end = [" + end + "]");
while (start < end) {
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
// System.out.println("ReverseWords.reverseString returned: sb = [" + sb + "]");
}
private void reverseEachWord(StringBuilder sb) {
int start = 0;
int end = 1;
int n = sb.length();
while (start < n) {
while (end < n && sb.charAt(end) != ' ') {
end++;
}
reverseString(sb, start, end - 1);
start = end + 1;
end = start + 1;
}
}
}
//解法二:创建新字符数组填充。时间复杂度O(n)
class Solution {
public String reverseWords(String s) {
//源字符数组
char[] initialArr = s.toCharArray();
//新字符数组
char[] newArr = new char[initialArr.length+1];//下面循环添加"单词 ",最终末尾的空格不会返回
int newArrPos = 0;
//i来进行整体对源字符数组从后往前遍历
int i = initialArr.length-1;
while(i>=0){
while(i>=0 && initialArr[i] == ' '){i--;} //跳过空格
//此时i位置是边界或!=空格,先记录当前索引,之后的while用来确定单词的首字母的位置
int right = i;
while(i>=0 && initialArr[i] != ' '){i--;}
//指定区间单词取出(由于i为首字母的前一位,所以这里+1,),取出的每组末尾都带有一个空格
for (int j = i+1; j <= right; j++) {
newArr[newArrPos++] = initialArr[j];
if(j == right){
newArr[newArrPos++] = ' ';//空格
}
}
}
//若是原始字符串没有单词,直接返回空字符串;若是有单词,返回0-末尾空格索引前范围的字符数组(转成String返回)
if(newArrPos == 0){
return "";
}else{
return new String(newArr,0,newArrPos-1);
}
}
右旋字符串
题目:
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
题解:
1.先把字符串全部反转过来 2.再字符串分为两个部分,前面后面分别进行反转
KMP算法
求前缀数组,前缀往后移动一位生成next数组,或者前缀的每一位都减1生成新的next数组
其他
java集合(容器)
概念:Java 集合,也叫作容器,主要是由两大接口派生而来:一个是 Collection
接口,主要用于存放单一元素;另一个是 Map
接口,主要用于存放键值对。对于Collection
接口,下面又有三个主要的子接口:List
、Set
、 Queue
。
List
(对付顺序的好帮手): 存储的元素是有序的、可重复的。Set
(注重独一无二的性质): 存储的元素不可重复的。Queue
(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。Map
(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),"x" 代表 key,"y" 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
和数组区别:
1.数组长度固定,集合长度不固定
2.数组可以存储基本类型和引用类型,集合只能存储引用类型
位置:java.util.*
Collection父接口
迭代器iterator
专门用来遍历集合的一种方式
Iterator it=collection.iterator();
//it.hasNext();有没有下一个元素;
//it.next();获取下一个元素
//it.remove();删除当前元素,不能使用collection的删除方法
while(it.hasNext()){
String s=(String)it.next();
System.out.println(s);
}
List子接口
List list=new ArrayList<>();
特点:有序(按照插入的顺序排),元素有下标,元素可以重复
遍历
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
//使用增强for
for(Object obj: list){
System.out.println(obj);
}
//使用普通迭代器
//使用ListIterator
ListIterator lit=list.listIterator();
while(lit.hasNext()){
System.out.println(lit.nextIndex()+":"+lit.next());//打印脚标和元素
}//从前往后遍历
while(it.hasPrevious()){
System.out.println(lit.previousIndex()+":"+lit.previous());
}//从后往前遍历
获取位置
System.out.println(list.indexof("华为"));//获取下标
PS:list中的元素可以自动装箱
//1.添加数字可以自动装箱
List list=new ArrayList();
list.add(20);//自动将基本数据类型变为包装类
//2.删除操作
list.remove(20);//删除下标为20的元素
list.remove(new Integer(20));//要转换为包装类才能进行删除
System.out.println(list.toString());
list.sublist(1,3);//返回下标为1和2的sublist
Arraylist
Arraylist:以数组结构实现,查询快,增删慢,线程不安全
Vector:数组结构实现,查询快,增删慢,线程安全
LinkedList:链表结构实现,增删快,查询慢
常见操作
1.删除操作
Student st1=new Student("小红",12);
Student st2=new Student("小张",34);
Student st3=new Student("小兰",18);
Arraylist alist=new ArrayList<>();
alist.add(st1);
alist.add(st2);
alist.add(st3);
//删除一个new出来的值相等的元素
//1.equals重载,使得只要值相等就可以视为同一个对象
pubulic boolean equals(Object obj){
if(this==obj){
return true;
}
if(this==null){
return false;
}
if(obj instanceof Student){
Student s=(Student)obj;
if(this.name.equals(s.getName())&&this.age==s.getAge()){
return true;
}
}
return false;
}
//2.进行remove
alist.remove(new Student("小红",12);
2.遍历元素
//1.使用迭代器
Iterator it=alist.Iterator();//注意new的是谁的迭代器
while(it.hasNext()){
Student s=(Student)it.next();
System.out.println(s.toString());
}
//2.使用列表迭代器ListIterator
ListIterator lit=alist.listIterator();
3.判断:contains();isEmpty()
4.查找:indexOf()
源码分析
DEFAULT_CAPACITY默认容量 =10;
注意:没有向集合中添加任何元素时,容量为0;添加一个元素之后,容量为10
elementData:存放元素的数组
size():实际元素个数
add():添加元素
元素个数超过时进行扩容,每次扩容后的大小为原来oldCapacity的1.5倍
Vecotr
因为是线程安全的,所以比ArrayList稍微安全一些
Vector的大小可以根据需要增大或缩小
Vector vector=new Vector<>();
//增加删除操作都和ArrayList相同
//遍历也可以用增强for
//使用枚举器
Enumeration en=vector.elements();
while(en.hasMoreElements){
String s=(String)en.nextElement();
System.out.println(s);
}
//其他方法:firstElement,lastElement,elementAt();
LinkedList
增删快,查询慢
存储结构:双向链表
LinkedList linkedlist=new LinkedList<>();
//增删和之前一样都用add和remove
//for遍历
for(int i=0;i<linkedlist.size();i++){
System.out.println(linkedlist.get(i));
}
//增强for遍历
for(Object obj: linkedlist){
Student s=(Student)obj;
System.out.println(s.toString());
}
//使用迭代器也和之前一样有Iterator和ListIterator两种
源码分析
size():集合大小
first:头结点
last:尾节点
ArrayList和LinkedList的区别
泛型<T,...>
泛型类,泛型接口,泛型方法
T是类型占位符,表示一种引用类型,如果编写多个使用多个逗号隔开
泛型类
注意:
1.不能用T直接new一个对象
T t1=new T();//这种方法是错误的,不能new T
2.泛型只能使用引用类型
MyGeneric<Integer> myGeneric2=new MyGeneric<Integer>();
//使用的是Integer类型不是Int类型
3.不同泛型对象不能相互复制
MyGeneric<String> myGeneric3=myGeneric2;//这种等值方法是不对的
//创建泛型类
public class MyGeneric<T>{
//创建变量
T t;
//作为方法的参数
public void show(T t){}
//作为方法的返回值
public T getT(){
return t;
}
}
//用泛型类创建对象
MyGeneric<String> myGeneric=new Mygeneric<String>();
泛型接口
//泛型接口
public interface MyInterface<T>{
String name="张三";
T server(T t);
}
//泛型类不确定类型
public class MyInterfaceImpl<T> implements MyInterface<T>{
public String server(String t){
return t;
}
//泛型类确定类型
public class MyInterfaceImp2 implements MyInterface<String>{
public String server(String t){
return t;
}
泛型方法
直接根据传入的参数的类型确定泛型的类型--提高代码重用性
泛型的使用还可以防止类型转换异常,提高代码的安全性
Set子接口
Character类
Java学习笔记——Character类_character java-CSDN博客
String类的常见用法
JAVA String的常见用法_java中string的用法-CSDN博客
String,StringBuffer和StringBuilder
String、StringBuffer和StringBuilder的详解_string stringbuffer stringbuilder-CSDN博客
【详解】String、StringBuffer、StringBuilder的基本用法及区别_stringbuffer与stringbuilder的使用-CSDN博客
CharSequence接口:
表示是一个字符序列,提供了一系列抽象方法,不能用在set和map这些类集中,String、StringBuffer、StringBuilder都实现了这个接口,因此他们都属于字符序列,并且要覆写CharSequence中的几个操作方法 。
Appendable接口:
表示实现此接口的子类可以对字符或字符串进行追加操作【提供有append(char c),append(CharSequence s)方法】,StringBuffer和StringBuilder都实现了这个接口,它们可以进行字符序列的追加操作 ,而String没有实现此接口,因此它不能进行拼接操作,也就不能改变内容。此接口并没有规定多线程访问是否安全,要在子类中规定。通过观察源码可以发现,StringBuilder和StringBuffer操作方法几乎一样,而唯一不同的是,StringBuffer中大部分方法都使用synchronized关键字声明,完成了线程了同步操作,而StringBuilder没有,因此,StringBuffer的安全性更好。但相反的,线程同步带来的问题就是性能问题,每条线程都要排队等待,而StringBuilder由于线程异步,具有更好的性能。
1.String常量的拼接效率优于String变量的拼接
常量拼接:编译阶段就已经连接起来,形成了一个字符串常量
变量拼接:str=s1+s2完成的是如下操作:
StringBuilder temp=new StringBuilder(s1),
temp.append(s2);
str=temp.toString();
2.大量字符串累加时,StringBuffer的append()效率远好于String对象的"累+"连接
因为String对象中的value[]是不能改变的,每一次合并后字符串值都需要创建一个新的String对象来存放。循环1W次自然需要创建1W个String对象和1W个StringBuilder对象,效率低就可想而知了。测试②中sb.append(s1);只需要将自己的value[]数组不停的扩大来存放s1即可。循环过程中无需在堆中创建任何新的对象。效率高就不足为奇了。
3.StringBuffer和String对比
//String (final不可变值)
public final class String
{
private final char value[];
public String(String original) {
// 把原字符串original切分成字符数组并赋给value[];
}
}
//StringBuffer (非final可变值)
public final class StringBuffer extends AbstractStringBuilder
{
char value[]; //继承了父类AbstractStringBuilder中的value[]
public StringBuffer(String str) {
super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组
append(str); //将str切分成字符序列并加入到value[]中
}
}
(1) String中的是常量(final)数组,只能被赋值一次。
(2) StringBuffer中的value[]就是一个很普通的数组,而且可以通过append()方法将新字符串加入value[]末尾。这样也就改变了value[]的内容和大小了。
java中length,size()和length()的区别
1 java中的length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了length这个属性.
2 java中的length()方法是针对字符串String说的,如果想看这个字符串的长度则用到length()这个方法.
3.java中的size()方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!
public static void main(String[] args) {
String []list={"ma","cao","yuan"};
String a="macaoyuan";
System.out.println(list.length);
System.out.println(a.length());
List array=new ArrayList();
array.add(a);
System.out.println(array.size());
}
//输出的值为:3 9 1
java类中的成员默认为包私有类型
在Java中,类(Class)的成员(包括字段、方法和内部类)的默认访问级别(也称为默认访问修饰符或包级私有)既不是private
也不是public
。当你没有明确地为类的成员指定访问修饰符时,它们就具有默认访问级别。
public
:成员可以被任何其他类访问。protected
:成员可以被同一个包内的其他类以及所有子类(无论子类位于哪个包)访问。- 默认(无修饰符,也称为包级私有):成员只能被同一个包内的其他类访问。
private
:成员只能被定义它们的类本身访问。
因此,如果你没有为类的成员(如字段、方法)指定任何访问修饰符,那么这些成员就只能被定义在同一个包内的其他类访问。这并不意味着它们是private
的(只能被类本身访问),也不意味着它们是public
的(可以被任何类访问)。它们只是包级私有的,即只能在同一个包内被访问。
Integer.MAX_VALUE
Integer.MAX_VALUE的含义_integer.maxvalue-CSDN博客
Math的用法
Java中的Math函数常用方法总结_java math-CSDN博客
toString()
toString()
方法是 JavaScript 中所有对象的一个常用方法,包括内置对象(如 Number, String, Boolean, Array, Date 等)和用户自定义的对象。这个方法的主要作用是将对象转换为一个字符串表示形式。
- 对于原始数据类型(如数字、字符串、布尔值),
toString()
方法会返回该值的字符串表示。例如,Number(123).toString()
返回"123"
,true.toString()
返回"true"
。 - 对于数组(Array),
toString()
方法会返回由数组中的每个元素的字符串表示形式拼接而成的一个以逗号分隔的字符串。例如,[1, 2, 3].toString()
返回"1,2,3"
。 - 对于对象(Object),如果对象没有自定义
toString()
方法,那么调用toString()
方法会返回类似[object Type]
的字符串,其中Type
是对象的类型。例如,({}).toString()
返回"[object Object]"
。但是,开发者可以通过在对象上定义自己的toString()
方法来改变这个行为。
java输入(import java.util.Scanner;)
1、导包:import java.util.Scanner;
2、创建Scanner类型的对象:Scanner scanner= new Scanner( System.in) ;
3、调用Scanner类的相关方法(next() / nextXxx()) ,来获取指定类型的变量
4、释放资源:调用Scanner对象的close()方法, scanner.close();
Java016——Java输入输出语句_java输出-CSDN博客