📜个人简介 |
⭐️个人主页:摸鱼の文酱博客主页🙋♂️
🍑博客领域:java编程基础,mysql
🍅写作风格:干货,干货,还是tmd的干货
🌸精选专栏:【Java】【mysql】 【算法刷题笔记】
🎯博主的码云gitee,平常博主写的程序代码都在里面。
🚀支持博主:点赞👍、收藏⭐、留言💬
🍭作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!
这里写目录标题
今日学习内容:一维数组 指针练习
今日刷题:
🎯1470. 重新排列数组
题目描述:给你一个数组 nums ,数组中有 2n 个元素,按 [x1,x2,…,xn,y1,y2,…,yn] 的格式排列。
请你将数组按 [x1,y1,x2,y2,…,xn,yn] 格式重新排列,返回重排后的数组。
也就是说:要把下标变成[1 ,1+n, 2, 2+n, … n]
🐾思路一:遍历原数组,按规律存入新数组
如果不使用额外变量来存储ret的下标,可以这样想:每次都要向ret数组中挨着存入两个数据,那么下次再向里面存的时候,下标就是第一次的二倍
public int[] shuffle(int[] nums, int n) {
int [] ret = new int[2*n];
for(int i=0;i<n;i++){
ret[i*2]=nums[i];
ret[i*2+1]= nums[n+i];
}
return ret;
}
🐾思路二:标记法
利用题目中限制每一个元素 nums[i] 都大于 0。我们可以使用负数做标记。
标记什么?标记当前 nums[i] 存储的数字,是不是重新排列后的正确数字。如果是,存负数;如果不是,存正数(即原本的数字,还需处理)。
我们每次处理一个 nums[i],计算这个 nums[i] 应该放置的正确位置 j。但是,nums[j] 还没有排列好,所以我们暂时把 nums[j] 放到 nums[i] 的位置上来,并且记录上,此时 nums[i] 的元素本来的索引是 j。现在,我们就可以安心地把 nums[i] 放到 j 的位置了。同时,因为这已经是 nums[i] 正确的位置,取负数,即标记这个位置已经存放了正确的元素。
之后,我们继续处理当前的 nums[i],注意,此时这个新的 nums[i],本来的索引是 j。所以我们根据 j 算出它应该存放的位置,然后把这个位置的元素放到 nums[i] 中,取负做标记。
这个过程以此类推。这就是代码中 while 循环做的事情。
直到 nums[i] 的值也是负数,说明 i 的位置也已经是重新排列后的正确元素了,我们就可以看下一个位置了。
在 for 循环中,如果某一个元素已经是小于零了,说明这个位置已经是正确元素了,可以忽略。
这个算法虽然有两重循环,但是时间复杂度是 O(n) 的,因为每个元素最多会被重新排列一次,然后会被最外面的 for 循环访问一次。一旦重新排列过,for 的访问只是一次 if 判断而已。
当然,最后,数组中的所有元素还需要从负数转换回正数。
class Solution {
public:
vector<int> shuffle(vector<int>& nums, int n) {
for(int i = 0; i < 2 * n; i ++)
if(nums[i] > 0){
// j 描述当前的 nums[i] 对应的索引,初始为 i
int j = i;
while(nums[i] > 0){
// 计算 j 索引的元素,也就是现在的 nums[i],应该放置的索引
j = j < n ? 2 * j : 2 * (j - n) + 1;
// 把 nums[i] 放置到 j 的位置,
// 同时,把 nums[j] 放到 i 的位置,在下一轮循环继续处理
swap(nums[i], nums[j]);
// 使用负号标记上,现在 j 位置存储的元素已经是正确的元素了
nums[j] = -nums[j];
}
}
for(int& e: nums) e = -e;
return nums;
}
};
🎯1929. 数组串联
题目描述:给你一个长度为 n 的整数数组 nums 。请你构建一个长度为 2n 的答案数组 ans ,数组下标 从 0 开始计数 ,对于所有 0 <= i < n 的 i ,满足下述所有要求:
ans[i] == nums[i]
ans[i + n] == nums[i]
具体而言,ans 由两个 nums 数组 串联 形成。返回数组 ans 。
🐾思路一:创建新数组
创建一个长度为原数组长度两倍的数组,然后遍历原数组,挨个将数据存入新数组
public int[] getConcatenation(int[] nums) {
int[] ret = new int[nums.length*2];
for (int i = 0; i < nums.length; i++) {
ret[i] = nums[i];
ret[i + nums.length] = nums[i];
}
return ret;
}
🐾思路二:STL
借助库函数进行复制
public int[] getConcatenation(int[] nums) {
int n = nums.length;
int[] ans = Arrays.copyOf(nums, n*2);
System.arraycopy(nums, 0, ans, n, n);
return ans;
}
🎯1920. 基于排列构建数组
问题描述:给你一个 从 0 开始的排列 nums(下标也从 0 开始)。请你构建一个 同样长度 的数组 ans ,其中,对于每个 i(0 <= i < nums.length),都满足 ans[i] = nums[nums[i]] 。返回构建好的数组 ans 。
从 0 开始的排列 nums 是一个由 0 到 nums.length - 1(0 和 nums.length - 1 也包含在内)的不同整数组成的数组。
🐾思路一:直接对原数组进行修改
为了使得构建过程可以完整进行,我们需要让nums 中的每个元素 nums[i] 能够同时存储「当前值」(即 nums[i])和「最终值」(即 nums[nums[i])。
我们注意到 \textit{nums}nums 中元素的取值范围为 [0, 999] 闭区间,这意味着 nums 中的每个元素的「当前值」和「最终值」都在[0,999] 闭区间内。
因此,我们可以使用类似「10001000 进制」的思路来表示每个元素的「当前值」和「最终值」。对于每个元素,我们用它除以 10001000 的商数表示它的「最终值」,而用余数表示它的「当前值」。
那么,我们首先遍历nums,计算每个元素的「最终值」,并乘以 10001000 加在该元素上。随后,我们再次遍历数组,并将每个元素的值除以 10001000 保留其商数。此时 nums 即为构建完成的数组,我们返回该数组作为答案。
public int[] buildArray(int[] nums) {
int n = nums.length;
// 第一次遍历编码最终值
for (int i = 0; i < n; ++i){
nums[i] += 1000 * (nums[nums[i]] % 1000);
}
// 第二次遍历修改为最终值
for (int i = 0; i < n; ++i){
nums[i] /= 1000;
}
return nums;
}
//极简:
public int[] buildArray(int[] nums) {
for(int i=0;i<nums.length;i++){nums[i]+=1000*(nums[nums[i]]%1000);}
for(int i=0;i<nums.length;i++){nums[i]/=1000;}
return nums;
}
🐾思路二:创建新数组
class Solution{
public int[] buildArray(int[] nums){
int[] res = new int[nums.length];
for(int i = 0;i<nums.length;i++){
res[i] = nums[nums[i]];
}
return res;
}
}
🐾思路三:使用api-- IntStream.range
range()方法,创建一个以1为增量步长,从startInclusive(包括)到endExclusive(不包括)的有序的数字流。
产生的IntStream对象,可以像数组一样遍历。
用法:
static IntStream range(int startInclusive,int endExclusive);
参数
IntStream : 原始整数值元素的序列
startInclusive : 规定了数字的起始值,包括
endExclusive : 规定了数字的上限值,不包括
返回值
一个int元素范围的顺序IntStream
举例:
public static void main(String[] args) {
IntStream intStream = IntStream.range(1,5);
intStream.forEach(System.out::println);
}
代码实现:
class Solution{
public int[] buildArray(int[] nums){
int[] res = new int[nums.length];
IntStream.range(0,nums.length).forEach(i->res[i] = nums[nums[i]]);
return res;
}
}
🎯1480. 一维数组的动态和
题目描述:给你一个数组 nums 。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]…nums[i]) 。
请返回 nums 的动态和。
🐾思路一:原地修改
每次让 nums[i] 变为 nums[i−1]+nums[i] 即可(因为此时的 nums[i−1] 即为 runningSum[i−1])。
public int[] runningSum(int[] nums) {
int n = nums.length;
for (int i = 1; i < n; i++) {
nums[i] += nums[i - 1];
}
return nums;
}
🐾思路二:创建新数组
创建新数组,然后遍历
public int[] runningSum(int[] nums) {
int n = nums.length;
int[] ans = new int[n];
for (int i = 0, s = 0; i < n; i++) {
ans[i] = s = s + nums[i];
}
return ans;
}
🎯剑指 Offer 58 - II. 左旋转字符串
问题描述:字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
🐾思路一:切片函数
public String reverseLeftWords(String s, int n) {
return s.substring(n, s.length()) + s.substring(0, n);
}
🐾思路二:列表遍历拼接
新建一个 StringBuilder(Java) ,记为 resres ;
先向 resres 添加 “第 n+1 位至末位的字符” ;
再向 resres 添加 “首位至第 n位的字符” ;
将 resres 转化为字符串并返回。
class Solution {
public String reverseLeftWords(String s, int n) {
StringBuilder res = new StringBuilder();
for(int i = n; i < s.length(); i++)
res.append(s.charAt(i));
for(int i = 0; i < n; i++)
res.append(s.charAt(i));
return res.toString();
}
}
//利用求余运算,可以简化代码
public String reverseLeftWords(String s, int n) {
StringBuilder res = new StringBuilder();
for(int i = n; i < n + s.length(); i++)
res.append(s.charAt(i % s.length()));
return res.toString();
}
🐾思路三:字符串遍历拼接
此方法与 方法二 思路一致,区别是使用字符串代替列表
class Solution {
public String reverseLeftWords(String s, int n) {
String res = "";
for(int i = n; i < s.length(); i++)
res += s.charAt(i);
for(int i = 0; i < n; i++)
res += s.charAt(i);
return res;
}
}
//利用求余运算简化代码
public String reverseLeftWords(String s, int n) {
String res = "";
for(int i = n; i < n + s.length(); i++)
res += s.charAt(i % s.length());
return res;
}
//或者也可以这么写:将前两个拿出来,最后接在末尾
//本质上思路是一样的
public String reverseLeftWords(String s, int n) {
StringBuilder sb = new StringBuilder(s);
String substring = sb.substring(0, n);
sb.delete(0, n);
sb.append(substring);
return sb.toString();
}
🐾思路四:一个一个逆置
每次都把第一个字符移动到末尾
public String reverseLeftWords(String s, int n) {
char[] arr = s.toCharArray();
int len = arr.length;
int j = 0;
for(j = 0; j < n; j++) //需要左旋几个 就循环几次
{ //每次都把字符串首位的字符放在末尾
//1.先把第一个要左旋的字符存起来
char tmp = arr[0];
//2.把后续的字符一次性往前移动
int i = 0;
for(i = 0;i < len - 1;i++)
{
arr[i] = arr[i+1];
}
//3.最后吧保存好的第一个字符加在最后
arr[len-1] = tmp;
}
return String.copyValueOf(arr);
}
🐾思路五:连续逆置三次
首先,先明确一个事情,题目所给的 n ,并不一定就必须小于字符串长度,当他小于字符串长度时,n 为几,那就是要旋转多少字符,那如果n大于字符串长度呢?
让我们先来看看 n=字符串长度时,旋转n个字符,那就相当于没有旋转,还是原来的字符串.
所以,按这样推理,n+1就是旋转1个 ,n+2就是旋转2个…那么也就是说,真正的旋转个数应该是 n%字符串长度
解题步骤:先拿 n%字符串长度 得到具体要左旋的字符个数k,
然后先把(0,k)范围的字符逆置,
然后把剩余部分(k+1,length-1)的字符逆置
最后把逆置后的字符串(0,length-1)再全体逆置一次
返回结果转为字符串
class Solution {
public String reverseLeftWords(String s, int n) {
char[] arr = s.toCharArray();
int len = arr.length;
n %= len; //计算字符左旋的个数
//连续三次逆置 以实现字符串指定字符左旋
//eg: a b c d e f n = 2
reverse(arr,0,n-1 ); //逆置前两个 :b a c d e f
reverse(arr, n,len-1); //逆置后四个 :b a f e d c
reverse(arr, 0,len-1); //整体全部逆置 :c d e f a b
//这样就通过逆置 实现了将a b整体移到字符串尾部
return String.copyValueOf(arr);
}
public static void reverse(char[] arr,int left,int right)
{
while(left<right)
{
char tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++;
right--;
}
}
}
🐾思路五:倍增字符串
将原字符串倍增,然后从下标等于n开始,存入一个长度和原字符数组长度相等的字符数粗中
public String reverseLeftWords(String s, int n) {
String s1 = s+s;
char[] arr = s1.toCharArray();
char[] arr2 = new char[arr.length/2];
for (int i=0;i<arr.length/2;i++){
arr2[i] = arr[i+n];
}
return String.copyValueOf(arr2);
}
🎯 1108. IP 地址无效化
题目描述:给你一个有效的 IPv4 地址 address,返回这个 IP 地址的无效化版本。
所谓无效化 IP 地址,其实就是用 “[.]” 代替了每个 “.”。
🐾思路一:直接使用 String 类的 replace
class Solution {
public String defangIPaddr(String address) {
return address.replace(".", "[.]");
}
}
🐾思路二:使用 address 创建 StringBuilder,遍历,遇到 ‘.’ 使用 insert。
class Solution {
public String defangIPaddr(String address) {
StringBuilder s = new StringBuilder(address);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '.') {
s.insert(i + 1, ']');// 先插入后面,此时 i 下标仍是'.'
s.insert(i, '[');// 插入 '.' 前面,此时 i 下标是'[' ,i+2 下标为']'
i += 3;// 故 i 直接加 3,为下一个字符,注意此时已经是原来 i+1 下标的字符了;
//此次循环结束进入下次循环还会进行加 1,不过又因为 ip 地址格式的原因,不会有连续的两个 '.' 连着;
//所以这个位置绝不可能是 '.',所以再加 1,也没问题。
}
}
return s.toString();
}
}
🐾思路三:创建空的 Stringbuilder ,遍历 address 进行 append。
创建空的 Stringbuilder ,遍历 address 进行 append。
**class Solution {
public String defangIPaddr(String address) {
StringBuilder s = new StringBuilder();
for (int i = 0; i < address.length(); i++) {
if (address.charAt(i) == '.') {
//s.append('[');
//s.append('.');
//s.append(']');
s.append("[.]");
} else {
s.append(address.charAt(i));
}
}
return s.toString();
}
}