重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
推荐10个常用的Java网站
创新互联是专业的射洪网站建设公司,射洪接单;提供成都网站建设、网站制作,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行射洪网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!
Stackoverflow
Stackoverflow.com 可能是编程界中最流行的网站了,该网站有成千上万个好问题和答案。学习API或者编程语言,通常依赖于代码示例,stackoverflow就可以提供大量的代码片段。
Stackoverflow的另一个优点在于它的社交性。你可以在一些特定的标签下查看问题,比如“Java”、“regex”,你就会看到哪些是高频问题。这对于学习,以及Java博主书写热门话题都是一种非常好的资源。
DZone
这是一个非常有趣的网站,有相当多的开发者在这个网站上分享他们博客文章。就像一场冒险一样,在这里,你永远不知道下一秒会看到什么内容。
LeetCode
如果有Java方面的面试问题,例如“在Java中,内存中的数组是什么样的”,你可以从很多Java教程中找到答案。但是,遇到类似于“如何将SortedArray转化成一个BalancedTree”这样的问题,你就应该去求助LeetCode了。LeetCode包含一系列算法问题, 它能为有关信息技术的面试提供一个社会性的平台。这个网站最好的地方就是,它可以通过大小不同的数据在线检查你的代码是否正确。很多人认为,面试成功的关键在于重复这三个重要步骤:编码-阅读-讨论。
Java SE技术文档
这个网站包含了在使用Java SE API时会用到的所有文档,即使你是Java开发牛人,我敢肯定在这里你仍能发现一些有用的东西。比如,你可以阅读一些像“Java类的本质”、“部署”这样的教程。
Github
你也许知道可以免费的将你的项目托管在哪,但你不一定知道它还是一个学习当今流行的Java库和框架的优秀资源。比如,你想学 Spring MVC Framework,你可以搜索并且找到一些开源的项目。遵循“照葫芦画瓢”的原则,你能够通过实例快速的学习框架,尤其是你已经具有使用类似框架的经验。
Coursera
这是有关视频讲座的最好网站,在这里你可以找到许多顶尖学校的著名教授的优秀计算机科学课程。这些教授中,有些甚至是一些计算机科学领域的发明者。
Java World
这个网站包含了大量的Java教程,涉及各种各样的话题。其中很多文章都写的很好,并且有图片、插图进行说明。它可以被用来当作深度学习的教科书。
DevStore
这个网站是针对开发者做的一个服务商店,为开发者提供从设计开发到运营推广一站式的解决方案。开发者在任何阶段需要某种服务时,都可通过DevStore进行参数对比,查看评测、快速体验等来了解,不再需要一一搜索。
Wikipedia 维基百科
这个网站是查询或学习几乎所有概念的最好的资源之一。例如,作为一个资深的Java开发人员,你可能只是想了解一些概念并不想深度研究。这里是一个可以免费查询最新信息的好地方。例如,什么是面向服务的编程?另外,它还能为你的写作提供大量素材。比如,同一个词在不同的领域可能会有完全不同的含义。有时,研究这些词在不同领域的不同含义是非常有意思的。
Program Creek
与以上几个网站相比,Programcreek.com在规模上要小很多。但是规模小也有优点,它在维护方面做的更好,读起来更有趣。在这里,你可以找到其他任何网站都不曾涉及到的话题,而且几乎每篇文章都包含漂亮的插图或者示例代码。它包含了来自不同领域的人发表的文章(科研、工业),并且经常为Java开发者提供优质材料。希望有一天,它也会出现在一些人的网站前十名排行名单中。
注:当然,对于Java开发牛人来说还有很多其它不错的网站。如果你认为有其他的网站也应该列在这里,请留言,我以后也会加进去的。谢谢。
LearnJava 在线 这是一个非常不错的学习 Java 的在线网站,纯免费。
Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。
Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。
可以。
准备华为matepad,AidLearning软件,其中华为应用市场中就有该软件,选择适合arm架构的JDK。
平板毕竟是更适合娱乐的设备,因此适合直接在平板上进行Java编程的软件还是很少的。虽然可以在应用市场找到一款名为Java编译器的软件,但是既没有智能提示,又没有代码补全,在上面刷题和直接在leetcode上刷没多大区别。
AIdLearning虽然是主打人工智能,但是它是把整个Debian系统搬到了安卓上,并且还内置了vscode这样,利用AidLearning中的vscode就可以比较简单的在手机上搭建Java编程环境了。AidLearning软件在应用市场中就可以搜到,直接安装即可。
前端刷题用js还是java
前端刷题用js还是java_用JavaScript刷LeetCode的正确姿势

韦桂超
原创
关注
0点赞·1164人阅读
虽然很多人都觉得前端算法弱,但其实 JavaScript 也可以刷题啊!最近两个月断断续续刷完了 leetcode 前 200 的 middle + hard ,总结了一些刷题常用的模板代码。走过路过发现 bug 请指出,拯救一个辣鸡(但很帅)的少年就靠您啦!
常用函数
包括打印函数和一些数学函数。
const _max =Math.max.bind(Math);
const _min=Math.min.bind(Math);
const _pow=Math.pow.bind(Math);
const _floor=Math.floor.bind(Math);
const _round=Math.round.bind(Math);
const _ceil=Math.ceil.bind(Math);
const log=console.log.bind(console);//const log = _ = {}
log 在提交的代码中当然是用不到的,不过在调试时十分有用。但是当代码里面加了很多 log 的时候,提交时还需要一个个注释掉就相当麻烦了,只要将 log 赋值为空函数就可以了。
举一个简单的例子,下面的代码是可以直接提交的。
//计算 1+2+...+n//const log = console.log.bind(console);
const log = _ ={}functionsumOneToN(n) {
let sum= 0;for (let i = 1; i = n; i++) {
sum+=i;
log(`i=${i}: sum=${sum}`);
}returnsum;
}
sumOneToN(10);
位运算的一些小技巧
判断一个整数 x 的奇偶性: x 1 = 1 (奇数) , x 1 = 0 (偶数)
求一个浮点数 x 的整数部分: ~~x ,对于正数相当于 floor(x) 对于负数相当于 ceil(-x)
计算 2 ^ n : 1 n 相当于 pow(2, n)
计算一个数 x 除以 2 的 n 倍: x n 相当于 ~~(x / pow(2, n))
判断一个数 x 是 2 的整数幂(即 x = 2 ^ n ): x (x - 1) = 0
※注意※:上面的位运算只对32位带符号的整数有效,如果使用的话,一定要注意数!据!范!围!
记住这些技巧的作用:
提升运行速度 ❌
提升逼格 ✅
举一个实用的例子,快速幂(原理自行google)
//计算x^n n为整数
functionqPow(x, n) {
let result= 1;while(n) {if (n 1) result *= x; //同 if(n%2)
x = x *x;
n= 1; //同 n=floor(n/2)
}returnresult;
}
链表
刚开始做 LeetCode 的题就遇到了很多链表的题。恶心心。最麻烦的不是写题,是调试啊!!于是总结了一些链表的辅助函数。
/**
* 链表节点
* @param {*} val
* @param {ListNode} next*/
function ListNode(val, next = null) {this.val =val;this.next =next;
}/**
* 将一个数组转为链表
* @param {array} a
* @return {ListNode}*/const getListFromArray= (a) ={
let dummy= newListNode()
let pre=dummy;
a.forEach(x= pre = pre.next = newListNode(x));returndummy.next;
}/**
* 将一个链表转为数组
* @param {ListNode} node
* @return {array}*/const getArrayFromList= (node) ={
let a=[];while(node) {
a.push(node.val);
node=node.next;
}returna;
}/**
* 打印一个链表
* @param {ListNode} node*/const logList= (node) ={
let str= 'list: ';while(node) {
str+= node.val + '-';
node=node.next;
}
str+= 'end';
log(str);
}
还有一个常用小技巧,每次写链表的操作,都要注意判断表头,如果创建一个空表头来进行操作会方便很多。
let dummy = newListNode();//返回
return dummy.next;
使用起来超爽哒~举个例子。@leetcode 82。题意就是删除链表中连续相同值的节点。
/** @lc app=leetcode id=82 lang=javascript
*
* [82] Remove Duplicates from Sorted List II*/
/**
* @param {ListNode} head
* @return {ListNode}*/
var deleteDuplicates = function(head) {//空指针或者只有一个节点不需要处理
if (head === null || head.next === null) returnhead;
let dummy= newListNode();
let oldLinkCurrent=head;
let newLinkCurrent=dummy;while(oldLinkCurrent) {
let next=oldLinkCurrent.next;//如果当前节点和下一个节点的值相同 就要一直向前直到出现不同的值
if (next oldLinkCurrent.val ===next.val) {while (next oldLinkCurrent.val ===next.val) {
next=next.next;
}
oldLinkCurrent=next;
}else{
newLinkCurrent= newLinkCurrent.next =oldLinkCurrent;
oldLinkCurrent=oldLinkCurrent.next;
}
}
newLinkCurrent.next= null; //记得结尾置空~
logList(dummy.next);returndummy.next;
};
deleteDuplicates(getListFromArray([1,2,3,3,4,4,5]));
deleteDuplicates(getListFromArray([1,1,2,2,3,3,4,4,5]));
deleteDuplicates(getListFromArray([1,1]));
deleteDuplicates(getListFromArray([1,2,2,3,3]));
本地运行结果
list: 1-2-5-end
list:5-end
list: end
list:1-end
是不是很方便!
矩阵(二维数组)
矩阵的题目也有很多,基本每一个需要用到二维数组的题,都涉及到初始化,求行数列数,遍历的代码。于是简单提取出来几个函数。
/**
* 初始化一个二维数组
* @param {number} r 行数
* @param {number} c 列数
* @param {*} init 初始值*/const initMatrix= (r, c, init = 0) = new Array(r).fill().map(_ = newArray(c).fill(init));/**
* 获取一个二维数组的行数和列数
* @param {any[][]} matrix
* @return [row, col]*/const getMatrixRowAndCol= (matrix) = matrix.length === 0 ? [0, 0] : [matrix.length, matrix[0].length];/**
* 遍历一个二维数组
* @param {any[][]} matrix
* @param {Function} func*/const matrixFor= (matrix, func) ={
matrix.forEach((row, i)={
row.forEach((item, j)={
func(item, i, j, row, matrix);
});
})
}/**
* 获取矩阵第index个元素 从0开始
* @param {any[][]} matrix
* @param {number} index*/
functiongetMatrix(matrix, index) {
let col= matrix[0].length;
let i= ~~(index /col);
let j= index - i *col;returnmatrix[i][j];
}/**
* 设置矩阵第index个元素 从0开始
* @param {any[][]} matrix
* @param {number} index*/
functionsetMatrix(matrix, index, value) {
let col= matrix[0].length;
let i= ~~(index /col);
let j= index - i *col;return matrix[i][j] =value;
}
找一个简单的矩阵的题示范一下用法。@leetcode 566。题意就是将一个矩阵重新排列为r行c列。
/** @lc app=leetcode id=566 lang=javascript
*
* [566] Reshape the Matrix*/
/**
* @param {number[][]} nums
* @param {number} r
* @param {number} c
* @return {number[][]}*/
var matrixReshape = function(nums, r, c) {//将一个矩阵重新排列为r行c列
//首先获取原来的行数和列数
let [r1, c1] =getMatrixRowAndCol(nums);
log(r1, c1);//不合法的话就返回原矩阵
if (!r1 || r1 * c1 !== r * c) returnnums;//初始化新矩阵
let matrix =initMatrix(r, c);//遍历原矩阵生成新矩阵
matrixFor(nums, (val, i, j) ={
let index= i * c1 + j; //计算是第几个元素
log(index);
setMatrix(matrix, index, val);//在新矩阵的对应位置赋值
});returnmatrix;
};
let x= matrixReshape([[1],[2],[3],[4]], 2, 2);
log(x)
二叉树
当我做到二叉树相关的题目,我发现,我错怪链表了,呜呜呜这个更恶心。
当然对于二叉树,只要你掌握先序遍历,后序遍历,中序遍历,层序遍历,递归以及非递归版,先序中序求二叉树,先序后序求二叉树,基本就能AC大部分二叉树的题目了(我瞎说的)。
二叉树的题目 input 一般都是层序遍历的数组,所以写了层序遍历数组和二叉树的转换,方便调试。
function TreeNode(val, left = null, right = null) {this.val =val;this.left =left;this.right =right;
}/**
* 通过一个层次遍历的数组生成一棵二叉树
* @param {any[]} array
* @return {TreeNode}*/
functiongetTreeFromLayerOrderArray(array) {
let n=array.length;if (!n) return null;
let index= 0;
let root= new TreeNode(array[index++]);
let queue=[root];while(index
let top=queue.shift();
let v= array[index++];
top.left= v == null ? null : newTreeNode(v);if (index
let v= array[index++];
top.right= v == null ? null : newTreeNode(v);
}if(top.left) queue.push(top.left);if(top.right) queue.push(top.right);
}returnroot;
}/**
* 层序遍历一棵二叉树 生成一个数组
* @param {TreeNode} root
* @return {any[]}*/
functiongetLayerOrderArrayFromTree(root) {
let res=[];
let que=[root];while(que.length) {
let len=que.length;for (let i = 0; i len; i++) {
let cur=que.shift();if(cur) {
res.push(cur.val);
que.push(cur.left, cur.right);
}else{
res.push(null);
}
}
}while (res.length 1 res[res.length - 1] == null) res.pop(); //删掉结尾的 null
returnres;
}
一个例子,@leetcode 110,判断一棵二叉树是不是平衡二叉树。
/**
* @param {TreeNode} root
* @return {boolean}*/
var isBalanced = function(root) {if (!root) return true; //认为空指针也是平衡树吧
//获取一个二叉树的深度
const d = (root) ={if (!root) return 0;return _max(d(root.left), d(root.right)) + 1;
}
let leftDepth=d(root.left);
let rightDepth=d(root.right);//深度差不超过 1 且子树都是平衡树
if (_min(leftDepth, rightDepth) + 1 =_max(leftDepth, rightDepth) isBalanced(root.left) isBalanced(root.right)) return true;return false;
};
log(isBalanced(getTreeFromLayerOrderArray([3,9,20,null,null,15,7])));
log(isBalanced(getTreeFromLayerOrderArray([1,2,2,3,3,null,null,4,4])));
二分查找
参考 C++ STL 中的 lower_bound 和 upper_bound 。这两个函数真的很好用的!
/**
* 寻找=target的最小下标
* @param {number[]} nums
* @param {number} target
* @return {number}*/
functionlower_bound(nums, target) {
let first= 0;
let len=nums.length;while (len 0) {
let half= len 1;
let middle= first +half;if (nums[middle]
first= middle + 1;
len= len - half - 1;
}else{
len=half;
}
}returnfirst;
}/**
* 寻找target的最小下标
* @param {number[]} nums
* @param {number} target
* @return {number}*/
functionupper_bound(nums, target) {
let first= 0;
let len=nums.length;while (len 0) {
let half= len 1;
let middle= first +half;if (nums[middle] target) {
len=half;
}else{
first= middle + 1;
len= len - half - 1;
}
}returnfirst;
}
照例,举个例子,@leetcode 34。题意是给一个排好序的数组和一个目标数字,求数组中等于目标数字的元素最小下标和最大下标。不存在就返回 -1。
/** @lc app=leetcode id=34 lang=javascript
*
* [34] Find First and Last Position of Element in Sorted Array*/
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}*/
var searchRange = function(nums, target) {
let lower=lower_bound(nums, target);
let upper=upper_bound(nums, target);
let size=nums.length;//不存在返回 [-1, -1]
if (lower = size || nums[lower] !== target) return [-1, -1];return [lower, upper - 1];
};
在 VS Code 中刷 LeetCode
前面说的那些模板,难道每一次打开新的一道题都要复制一遍么?当然不用啦。
首先配置代码片段 选择 Code - Preferences - User Snippets ,然后选择 JavaScript
然后把文件替换为下面的代码:
{"leetcode template": {"prefix": "@lc","body": ["const _max = Math.max.bind(Math);","const _min = Math.min.bind(Math);","const _pow = Math.pow.bind(Math);","const _floor = Math.floor.bind(Math);","const _round = Math.round.bind(Math);","const _ceil = Math.ceil.bind(Math);","const log = console.log.bind(console);","// const log = _ = {}","/**************** 链表 ****************/","/**"," * 链表节点"," * @param {*} val"," * @param {ListNode} next"," */","function ListNode(val, next = null) {"," this.val = val;"," this.next = next;","}","/**"," * 将一个数组转为链表"," * @param {array} array"," * @return {ListNode}"," */","const getListFromArray = (array) = {"," let dummy = new ListNode()"," let pre = dummy;"," array.forEach(x = pre = pre.next = new ListNode(x));"," return dummy.next;","}","/**"," * 将一个链表转为数组"," * @param {ListNode} list"," * @return {array}"," */","const getArrayFromList = (list) = {"," let a = [];"," while (list) {"," a.push(list.val);"," list = list.next;"," }"," return a;","}","/**"," * 打印一个链表"," * @param {ListNode} list "," */","const logList = (list) = {"," let str = 'list: ';"," while (list) {"," str += list.val + '-';"," list = list.next;"," }"," str += 'end';"," log(str);","}","/**************** 矩阵(二维数组) ****************/","/**"," * 初始化一个二维数组"," * @param {number} r 行数"," * @param {number} c 列数"," * @param {*} init 初始值"," */","const initMatrix = (r, c, init = 0) = new Array(r).fill().map(_ = new Array(c).fill(init));","/**"," * 获取一个二维数组的行数和列数"," * @param {any[][]} matrix"," * @return [row, col]"," */","const getMatrixRowAndCol = (matrix) = matrix.length === 0 ? [0, 0] : [matrix.length, matrix[0].length];","/**"," * 遍历一个二维数组"," * @param {any[][]} matrix "," * @param {Function} func "," */","const matrixFor = (matrix, func) = {"," matrix.forEach((row, i) = {"," row.forEach((item, j) = {"," func(item, i, j, row, matrix);"," });"," })","}","/**"," * 获取矩阵第index个元素 从0开始"," * @param {any[][]} matrix "," * @param {number} index "," */","function getMatrix(matrix, index) {"," let col = matrix[0].length;"," let i = ~~(index / col);"," let j = index - i * col;"," return matrix[i][j];","}","/**"," * 设置矩阵第index个元素 从0开始"," * @param {any[][]} matrix "," * @param {number} index "," */","function setMatrix(matrix, index, value) {"," let col = matrix[0].length;"," let i = ~~(index / col);"," let j = index - i * col;"," return matrix[i][j] = value;","}","/**************** 二叉树 ****************/","/**"," * 二叉树节点"," * @param {*} val"," * @param {TreeNode} left"," * @param {TreeNode} right"," */","function TreeNode(val, left = null, right = null) {"," this.val = val;"," this.left = left;"," this.right = right;","}","/**"," * 通过一个层次遍历的数组生成一棵二叉树"," * @param {any[]} array"," * @return {TreeNode}"," */","function getTreeFromLayerOrderArray(array) {"," let n = array.length;"," if (!n) return null;"," let index = 0;"," let root = new TreeNode(array[index++]);"," let queue = [root];"," while(index n) {"," let top = queue.shift();"," let v = array[index++];"," top.left = v == null ? null : new TreeNode(v);"," if (index n) {"," let v = array[index++];"," top.right = v == null ? null : new TreeNode(v);"," }"," if (top.left) queue.push(top.left);"," if (top.right) queue.push(top.right);"," }"," return root;","}","/**"," * 层序遍历一棵二叉树 生成一个数组"," * @param {TreeNode} root "," * @return {any[]}"," */","function getLayerOrderArrayFromTree(root) {"," let res = [];"," let que = [root];"," while (que.length) {"," let len = que.length;"," for (let i = 0; i len; i++) {"," let cur = que.shift();"," if (cur) {"," res.push(cur.val);"," que.push(cur.left, cur.right);"," } else {"," res.push(null);"," }"," }"," }"," while (res.length 1 res[res.length - 1] == null) res.pop(); // 删掉结尾的 null"," return res;","}","/**************** 二分查找 ****************/","/**"," * 寻找=target的最小下标"," * @param {number[]} nums"," * @param {number} target"," * @return {number}"," */","function lower_bound(nums, target) {"," let first = 0;"," let len = nums.length;",""," while (len 0) {"," let half = len 1;"," let middle = first + half;"," if (nums[middle] target) {"," first = middle + 1;"," len = len - half - 1;"," } else {"," len = half;"," }"," }"," return first;","}","","/**"," * 寻找target的最小下标"," * @param {number[]} nums"," * @param {number} target"," * @return {number}"," */","function upper_bound(nums, target) {"," let first = 0;"," let len = nums.length;",""," while (len 0) {"," let half = len 1;"," let middle = first + half;"," if (nums[middle] target) {"," len = half;"," } else {"," first = middle + 1;"," len = len - half - 1;"," }"," }"," return first;","}","$1"],"description": "LeetCode常用代码模板"}
}
java1+x中级只刷题能过,
其实这个问题是因人而异,如果你基础知识牢固,学习能力强,有工作经验,刷几套真题对于通过中级会计考试是没问题的;但是如果你基础薄弱,学习能力弱,只是刷几套真题,考过的可能性不大。 建议时间充足的话还是要全面掌握
大家好,我是老三,一个刷题困难户,接下来我们开始数组类型算法的刷题之旅!
数组
数组基本上是我们最熟悉的数据结构了,刚会写“Hello World”不久,接着就是“杨辉三角”之类的练习。
数组结构
上图是一个字符数组的例子。
因为内存空间连续,所以可以直接通过下标获取对应的元素。
但是删除就麻烦一点,相当于填坑,一个元素被移走,留下的坑,需要其它元素来填上。
删除元素
在Java中,多维数组的存储本质上也是一个行优先的一维数组。
我们都知道,在Java中的 “=” 用在基本数据类型上,是值传递,用在引用数据类型上,是引用传递。
这一块展开可以写一篇文章,我们只简单看个例子:
大家可以看到,newArray改变了,array也跟着变了。
为什么呢?
在Java中,数组是引用数组类型。array、newArray都是存储在栈中的引用,它们指向堆中真正存储的数组对象。
所以改变了newArray,实际是改变了newArray指向的数组。
数组引用传递
这一点是我们刷题需要注意的,复制数组需要在循环中一个个复制。
题目:704. 二分查找 ()
难度:简单
描述:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
题目示例
思路:
二分查找可以说我们都很熟了。
因为数组是有序的,所以定义三个指针,low、high、mid,每次与中间指针指向的元素nums[mid]比较,
二分查找
但是这个代码还有一处问题,在哪呢?
int mid = (left + right) / 2;
这个地方可能会因为left和right数值太大导致内存溢出,所以应该写为 int mid = left + ((right - left) 1);
修改之后代码如下:
时间复杂度:O(logn)
题目:35. 搜索插入位置 ()
难度:简单
描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
题目示例
思路:
二分查找比较简单,但写对还要费点功夫,再做一道基本一样的题巩固一下。
这道题基本一样,插入的位置可能有四种情况:
二叉树插入位置
代码如下:
时间复杂度:O(logn)
题目:34. 在排序数组中查找元素的第一个和最后一个位置 ()
难度:中等
描述:
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
题目示例
思路:
看到时间复杂度 O(log n) ,数组有序,我们知道,二分查找该上场了。
但是这道题有点不一样,它需要寻找边界。
那我们怎么办呢?
这就引入了寻找边界的二分查找。
这道题的思路是什么呢?
我们分别用二分查找来寻找左边界和右边界。
一般的二分查找:
注意,我们这里的返回条件是 nums[mid] == target ,但是寻找边界的时候就不能这样了,因为我们不能确定mid是不是我们的边界。
以寻找左边界为例,条件是 target = nums[mid] 的时候,我们接着往左移动。
寻找右边界也类似。
代码如下:
时间复杂度:O(logn)
题目:27. 移除元素 ()
难度:简单
描述:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
思路
「暴力解法」
暴力解法没什么好说的,和上道题类似,找到要删除的元素,把它后面的元素全部向前移动一位。
暴力解法
这里有两点需要注意:
代码如下:
时间复杂度:O(n²)。
「双指针法」
双指针法,是数组和链表题中非常常用的一种方法。
这道题用双指针法怎么解决呢?
定义两个指针,一个前,一个后。没有找到目标的时候front和after一起移动,找到目标的时候,after停下来,front接着移动,把front指向的值赋给after指向的值。
这样一来,双指针就通过一个循环完成了双循环完成的事情。
双指针法
代码如下:
时间复杂度:O(n)。
题目:27. 移除元素 ()
难度:简单
描述:
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
题目示例
思路
趁着上一道题劲儿还没缓过来,赶紧做一道基本一样的巩固一下。
直接上代码:
时间复杂度:O(n)。
题目:283. 移动零 ()
难度:简单
描述:
给定一个数组 nums ,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
「示例:」
「说明」 :
思路
继续沿着上一道题的思路。
移动零
代码如下:
时间复杂度:O(n)。
题目:977. 有序数组的平方 ()
难度:简单
描述:
给你一个按 「非递减顺序」 排序的整数数组 nums ,返回 「每个数字的平方」 组成的新数组,要求也按 「非递减顺序」 排序。
题目示例
思路
「暴力排序法」
这道题一看,最直观的做法是什么呢?
先求数字平方的数组,然后再把新数组排序。
代码也好写:
时间复杂度:遍历时间复杂度O(n),快排时间复杂度O(nlogn),所以时间复杂度O(n+nlogn)。
思路
「双指针法」
我们连写几道双指针了,这道题能不能用双指针实现呢?
我们分析一下,这个数组在取平方之前,是有序的,那么它绝对值最大的数一定是在两端的。
所以我们可以定义两个指针,一个指向最左端,一个指向最右端,比较两者平方的大小,大的平方放入结果数组,并移动指针。
有序数组的平方
代码如下:
时间复杂度:O(n)。
题目:1. 两数之和 ()
难度:简单
描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
题目示例
思路:
「暴力解法」
上来我们先来个最简单的暴力解法,大家应该都知道冒泡排序吧,类似的两层循环。
两层循环
代码写起来也很简单:
时间复杂度:看到这个双循环,就知道时间复杂度O(n²)。
「哈希辅助法」
时间复杂度O(n²)多少有点过了,这道题的重点是两个元素相加之和的判断。
我们可以用一个Hash集合把元素存起来,这样一来遍历一遍就够了,例如目标和9,当前元素2,只需要判断集合里是否有元素7就行了。
时间复杂度:从Hash查询和取值时间复杂度都是O(1),所以整体时间复杂度是O(1)。
题目:15. 三数之和 ()
难度:简单
描述:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为0且不重复的三元组。
注意:答案中不可以包含重复的三元组。
题目示例
思路:
「哈希法」
做完两数之和以后,我们首先想到的就是哈希法。
两层循环,取到a,b,再通过 0-(a+b) 来确定c。
但是这里还有一个问题, 答案中不可以包含重复的三元组。
所以,我们还要想办法去掉Hash里的重复元素。
可以加入一个约束,第三个数的索引大于第二个数才存入。
时间复杂度:双循环,O(n²)。
虽然这么也写出来了,但是,说实话,很难写出没有问题的代码。
我们写了这么多双指针,那么有没有可能用双指针的方式呢?
「双指针法」
首先对数组进行排序,然后遍历数组。
然后再在当前节点后面取左右指针,判断左右指针的值是否等于0-nums[i],然后分别左右移动。
怎么去重呢?
满足条件时,看左指针的值是否和前一个位置相等,右指针的值是否和和它后一个位置的值相等。
双指针法
代码如下:
时间复杂度:O(n²)
题目:18. 四数之和 ()
难度:简单
描述:
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:答案中不可以包含重复的四元组。
题目示例
思路:
我们延续三数之和的思路,在三数之和外面再套一层循环。
时间复杂度:O(n³)
题目:209. 长度最小的子数组()
难度:中等
描述:
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
题目示例
思路
这道题是一道经典的滑动窗口问题[4]。
image-20210801164436322
代码如下:
时间复杂度:O(n),虽然循环里套循环了,但是starrt和end各自被移动了n次,所以时间复杂度是O(n)。
题目:219. 存在重复元素 II ()
难度:简单
描述:
给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。
题目示例
思路:
上面我们做了一道滑动窗口的题,我们接着再做一道也可以用滑动窗口解决的问题。
这道题的滑动窗口略有区别,上一道题的窗口是活动的,这个是 固定的滑动窗口 ,维护一个长度为k的固定窗口,如果窗口内含有目标值,返回。如果窗口进入新的元素,就需要把头部的元素移除掉,保持窗口的长度。
固定窗口
代码如下:
时间复杂度:O(n)。
题目:1052. 爱生气的书店老板()
难度:中等
描述:
今天,书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。
在某些时候,书店老板会生气。如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。
书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。
请你返回这一天营业下来,最多有多少客户能够感到满意。
「示例:」
思路:
这道题是一道固定窗口的问题。
整体思路就是把不生气的部分作为固定窗口,固定窗口把customers分成了三部分,最后求三部分的最大和。
固定窗口
时间复杂度:O(n)。
空间复杂度:O(1)。
题目:面试题3. 数组中重复的数字 ()
难度:复杂
描述:
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
「示例 1:」
思路:
「哈希法」
这种找重复的数字问题,我们脑子里第一下就想起来,用Hash存储元素,然后进行比对。
代码实现也很简单:
时间复杂度:O(n)。
空间复杂度:O(n)
但今天的主角不是它,而是
「原地置换法」
我们注意到一个条件 所有数字都在 0 n-1 的范围内 ,那就在这方面进行操作,我们可以把元素放到它的值对应的下标的位置。
例如 num[2]=1,那我们就把它放到下标1的位置。
接着遍历,元素发现它应该待的坑已经被它的双胞胎兄弟给占了,它就知道,它是多余的那个。
原地置换
代码如下:
时间复杂度:O(n)。
空间复杂度:O(1)
题目:41. 缺失的第一个正数 ()
难度:复杂
描述:
给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。
题目示例
思路
「辅助数组」
这道题有一个非常巧妙地的办法![1]
可以引入一个辅助数组,从1开始,在对应的位置存入原数组对应的元素。如原数组num[0]=1,那么这个元素就应该存入辅助数组 helper[1]。
然后遍历辅助数组,发现的第一个坑就是缺失的第一个正数。
辅助数组
代码如下:
时间复杂度:O(n)。
空间复杂度:O(n)。
「原地置换法」
我们上面用了原地置换法解决了一个问题,降低了空间复杂度,我们这道题是不是也可以呢?
原地置换没法修改数组长度,我们肯定不能nums[i] 存 i 了,我们左移一下,num[i-1]存i。
原地置换
代码实现如下:
时间复杂度:O(n)。
空间复杂度:O(1)。
题目:54. 螺旋矩阵 ()
难度:中等
描述:
给你一个 m 行 n 列的矩阵 matrix ,请按照 「顺时针螺旋顺序」 ,返回矩阵中的所有元素。
示例 1:
示例2
思路
这道题,思路比较容易想,就是上右下左四个方向顺时针遍历数组。
顺时针遍历数组
但是这道题的细节是魔鬼。
有两种,一种是一圈遍历完成,上下左右的位置移动,遍历是左闭右开[的条件。
我们采用的是第二种,每遍历完一条边,就移动对应的位置,遍历就是左闭右闭的条件。
还有一点细节就是值得注意的是,遍历过程中可能会出现出现 top bottom || left right ,其中一对边界彼此交错了。
这意味着此时所有项都遍历完了,如果没有及时 break ,就会重复遍历。
代码如下:
时间复杂度:O(mn),其中 m 和 n 分别是输入矩阵的行数和列数。
题目:59. 螺旋矩阵 II ()
难度:中等
描述:
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
示例
思路
和上面一道题基本一模一样,我们往里面套就行了。
代码如下:
时间复杂度:O(n²)
剑指 Offer 29. 顺时针打印矩阵 也是一道类似的题目。
写了个顺口溜总结一下: