文章目录
1,查找数组中的重复数字
-
题目描述:
- 在一个长度为
n
的数组nums
里的所有数字都在0~n-1
的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
- 在一个长度为
1.1,思路一
- 这个题目是一道简单题目,最容易想到的是对数组中的元素一趟排序操作,然后在扫描一趟数组,就可以查找出重复的元素。但是这样的话排序是这里面最耗时间的地方,排序一个长度为
n
的数组的时间复杂度是o(nlogn)
。 - 代码实现:
/**
* 排序方法查找数组中的重复元素
* @param arr 待排序数组
* @return 查找到重复元素就返回,否则返回-1
*/
public static int duplicate02(int []arr){
int len=arr.length;
if(len == 0){
return -1;
}
// 使用工具类,对数组进行排序操作
Arrays.sort(arr);
for(int i=1;i<len;i++){
if(arr[i] == arr[i-1]){
return arr[i-1];
}
}
return -1;
}
- 时间复杂度:
o(nlogn)
。
1.2,思路二
- 方法二我们可以使用一个哈希表来实现算法,扫描一遍数组,如果当前元素已经存在于哈希表中,就把当前的元素返回,否则,就把当前元素添加到哈希表中,接着判断下一个元素,不过这样做是以牺牲空间为代价,换取时间的效率。
- 代码实现:
/**
* 使用哈希表,空间复杂度是o(n)
* @param arr 待查找的数组
* @return 查找到重复元素就返回,否则返回-1
*/
public static int duplicate01(int []arr){
int len=arr.length;
if(len == 0)
return -1;
// 创建一个哈希表,存储我们的数据
Map hashTable=new Hashtable(len);
for (int i=0;i<len;i++){
if(hashTable.containsValue(arr[i])){
// 判断哈希表中是否包含元素,如果没有包含,就添加到哈希表中,如果包含,就把此元素返回
return arr[i];
}else {
hashTable.put(i,arr[i]);
}
}
return -1;
}
- 空间复杂度:
o(n)
。 - 时间复杂度:
o(n)
。
1.3,思路三
- 通过审题,我们发现数组中的数字都在
0-n-1
的范围之内,如果这个数组中没有重复的数字,那么每一个元素就可以对应的放在和其下表一样的位置,比如元素5就放在下表为5的位置。所以,基于此想法,我们可以对数组重拍操做,当我们扫描到下表为i
的数字时候,首先比较这个数组(m
)是否等于i
,如果是,就接着比较下一个数字,如果不是,则再拿他和第m
个数字进行比较,如果他和第m
个数字相等,那么就找到一个重复的元素,如果和第m
个元素不相等,就把第i
个数字和第m
个数字进行交换,把m
放到属于他的位置,接下来重复这个比较,直到发现一个重估数字为止。 - 代码实现
/**
*
* @param arr 待查找的数组
* @return 查找到重复元素就返回,否则返回-1
*/
public static int duplicate(int arr[]){
int len=arr.length;
if(len == 0){
return -1;
}
int temp=0;
for(int i=0;i<len;i++){
temp = arr[i];
while (temp != i) {
// 如果当前元素和元素的下表不是一样的,那么就把当前元素放入和其下表值一样的位置
// 在这里是判断元素是否重复
if(arr[i] == arr[temp])
return arr[i];
// 交换元素
arr[i] = arr[temp];
arr[temp] = temp;
temp = arr[i];
}
}
return -1;
}
- 时间复杂度:
0(n)
.
1.4,思路四
- 使用一个辅助的数组,数组默认初始化为0,然后遍历数字数组,每遇到一个数字,就把这个数字对应的空数组里面的数值修改为1,当再次遍历到这个数值时,说明重复,直接的返回即可。
- 代码实现
class Solution {
public int findRepeatNumber(int[] nums) {
int [] tempArr = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
if (tempArr[nums[i]] == 0) {
tempArr[nums[i]] = 1;
} else {
return nums[i];
}
}
return 0;
}
}
- 时间复杂度:
0(n)
。
2,二维数组中元素查找
-
题目描述
-
在一个
n * m
的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
2.1,思路一
- 通过审题,简单判断一下就是在二维数组中查找一个元素值,很自然的可以想到通过一个双层循环,逐个元素的遍历进行查找,但是时间复杂度比较的高,达到
n
的平方级别。 - 代码实现
/**
* 二维数组中元素查找
* @param arr 待查找的数组
* @param num 查找元素
* @return 查找成功返回元素值,否则返回-1
*/
public static int searchNum01(int [][]arr,int num){
for(int i=0;i<arr.length;i++){
for(int j=0;j<arr[0].length;j++){
if(arr[i][j] == num){
return arr[i][j];
}
}
}
return -1;
}
- 时间复杂度:
o(n2)
。
2.2,思路二
- 思路一的解答方式很容易想到,但是我们写程序的要求是尽量的避免时间和空间复杂度很高的算法,所以尽管方法一容易想到,但是我们还是不使用此方法。题目中说到,在一个
n*m
的二维数组中,每一行元素从左到右都是递增的顺序,每一列元素从上到下也是递增的,所以这里是我们的解题点,如果我们使用第一种方式查找元素,那么我们每一次只能排除一个元素,但是我们能不能一次比较,排除大于一个的元素呢?实际是可以的,二维数组中第一行元素,是每一列中元素值最小的,所以,我们可以以列为单位,把我们要查找的元素和每一列的第一个元素比较,如果当前需要查找的元素小于某一列的第一个元素,那么此时我们就可以把当前的一整列元素全部排除掉,接着判断下一列,如果当前元素大于某一列的第一个元素,那么我们就可以在行的基础上比较下一个元素,逐个判断每一行的元素,这样,从列的角度看,好的情况下,我们每一次比较可以排除一行的元素,效率大大的提高了。文字看起来不好理解,我们用图说话。
通过上面的例子,我们发现,一次判断,我们赛选掉的元素不在是一个,而是基于行或者列的排除,所以大大提高了算法的效率。
- 代码实现
/**
* 二位数组中元素的搜索
* @param arr 待查找的数组
* @param num 查找元素
* @return 查找成功返回元素值,否则返回-1
*/
public static int searchNum(int [][]arr,int num){
int len=arr[0].length-1;
int i=0,j=len;
// 控制二维数组下表,防止越界
while ((i<arr.length)&&(j>=0)){
// 纵向判断
if(arr[i][j] > num){
j--;
}else {
// 横向判断
if(num == arr[i][j]){
return arr[i][j];
}
i++;
}
}
return -1;
}
- 时间复杂度:
o(n)
3,空格替换
- 题目描述
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
3.1,思路一
思路一可能是最容易想到的一种方式,当我们遇到一个空格之后,我们把当前空格之后的字符全部向后面移动两格,重复操作,直到我们遍历完毕字符串为止,但是这种方法的时间复杂度很高,是o(n2)
级别,这种方式我们一般不推荐使用。
- 时间复杂度:
o(n2)
。
3.2,思路二
思路一的时间复杂度很高,能不能只对字符串遍历一遍,就可以把空格处全部填充完毕,当然可以,我们可以先对字符串做一遍遍历操作,统计出字符串中空格的个数,然后开辟新的数组空间,大小是字符串的长度+空格数的2倍,然后我们在从后向前遍历字符串,遍历的时候直接把字符逐个放入新的数组中,遇到一个空格之后,新数组就向前面移动三个空格,然后插入一个%20
即可,这样,只需要对字符串进行一遍遍历操作即可填充,降低了时间复杂度。
- 代码实现
public class ReplaceSpaceDemo {
public static void main(String[] args) {
char []ch={'w','e',' ','a','r','e',' ','h','a','p','p','y'};
// System.out.println(ch.length);
char c[]=replace(ch);
System.out.println(Arrays.toString(c));
}
/**
* 空格替换
* @param ch 字符串 返回替换后的字符串
* @return
*/
public static char[] replace(char []ch){
// 首先获取字符串的长度
int len=ch.length;
int num=0;
// 统计空格的个数
for(int i=0;i<len;i++){
if(ch[i] == ' '){
num++;
}
}
// 开辟一个新的字符串
char c[]=new char[len+num*2];
for (int j=c.length-1,i=ch.length-1;j>=0;j--){
if(ch[i] != ' '){
// 如果当前字符不是等于空那么就复制到新数组中
c[j]=ch[i];
}else {
// 如果当前字符是空的,那么就填充字符
c[j--]='0';
c[j--]='2';
c[j]='%';
}
i--;
}
return c;
}
}
时间复杂度:O(N)
3.3,思路三
先求出题目中字符串的长度,然后对字符串中逐个字符进行判断,如果遇到空字符,就在新的字符串末尾加%20
,直到遍历完整个字符串,然后把新的字符串赋值给s串即可。
- 代码实现
class Solution {
public String replaceSpace(String s) {
int len=s.length();
String str=s;
s="";
for(int i=0;i<len;i++){
if(str.charAt(i)== ' '){
s=s.concat("%20");
}else{
s=s.concat(str.charAt(i)+"");
}
}
return s;
}
}
时间复杂度:o(n)
3.4,思路四
- 可以先把字符串对象转换为一个字符数组,然后对数组中的字符逐个遍历,使用StringBuilder来接受新的字符串对象,每次遇到空格时,就追加%20即可。
- 代码实现
class Solution {
public String replaceSpace(String s) {
char array[]=s.toCharArray();//字符串转换字符数组方法
//stringBuilder每一次都在原始字符串上追加,不会创建新的字符串
StringBuilder stringBuilder=new StringBuilder();
for (char ch:array){//增强的for循环
if(ch == ' ')
{
stringBuilder.append("%20");
}else {
stringBuilder.append(ch);
}
}
s=stringBuilder.toString();
return s;
}
}
- 时间复杂度:
O(n)
;
4,从尾到头打印链表
-
题目描述
-
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
4.1,思路一
思路一是我们重构链表,也就是我们采用头插法重新建立链表,这样只需要遍历一遍链表即可,但是这样也有缺点,就是我们链表原有的结构已经被改变。
- 代码实现
vector<int> reversePrint(ListNode* head) {
vector<int> res;
ListNode* pre = NULL;
ListNode* cur = head;
while(cur != nullptr){
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
while(pre){
res.push_back(pre->val);
pre = pre->next;
}
return res;
}
- 时间复杂度:
o(n)
4.2,思路二
逆序打印链表,很明显是符合栈的存储方式,所以我们也可以借助栈结构进行遍历。
- 代码实现
class Solution {
public int[] reversePrint(ListNode head) {
Stack<ListNode> stack = new Stack<ListNode>();
ListNode temp = head;
while (temp != null) {
stack.push(temp);
temp = temp.next;
}
int size = stack.size();
int[] print = new int[size];
for (int i = 0; i < size; i++) {
print[i] = stack.pop().val;
}
return print;
}
}
- 时间复杂度:
O(N)
4.3,思路三
如果我们知道了链表中节点的个数,那么我们的数组容量大小也就确定了,所以只需要从头到尾逐个遍历链表即可,然后把遍历的元素从数组的后面向前放即可。
- 代码实现
class Solution {
public static int[] reversePrint(ListNode head) {
ListNode node = head;
int count = 0;
while (node != null) {
++count;
node = node.next;
}
int[] nums = new int[count];
node = head;
for (int i = count - 1; i >= 0; --i) {
nums[i] = node.val;
node = node.next;
}
return nums;
}
}
- 时间复杂度:
O(n)
持续更新…
欢迎关注作者个人公众号,专注分享java,大数据,python,以及计算机基础知识。