return new int[0];
}
/**
- 方法二:哈希表
- 时间复杂度:O(N),其中N是数组的元素数量
- 空间复杂度:O(N),其中N是数组的元素数量
- @param numbers int类型的数组
- @param target 目标值
- @return 结果
*/
private static int[] hashTwoSum(int[] numbers, int target) {
Map<Integer, Integer> hashMap = new HashMap<>();
for (int i = 0, length = numbers.length; i < length; i++) {
int other = target - numbers[i];
if (hashMap.containsKey(other)) {
return new int[]{hashMap.get(other), i};
}
hashMap.put(numbers[i], i);
}
return new int[0];
}
}
Kotlin
/**
- Created by TanJiaJun on 2021/6/5.
-
- 两数之和(Two Sum)
- 难度:简单
- @see Two Sum
*/
object TwoSumKotlin {
@JvmStatic
fun main(args: Array) {
// 示例一
println(“示例一”)
val firstNumbers = intArrayOf(2, 7, 11, 15)
val firstTarget = 9
println(hashTwoSum(firstNumbers, firstTarget).contentToString())
print(“\n”)
// 示例二
println(“示例二”)
val secondNumbers = intArrayOf(3, 2, 4)
val secondTarget = 6
println(hashTwoSum(secondNumbers, secondTarget).contentToString())
print(“\n”)
// 示例三
println(“示例三”)
val thirdNumbers = intArrayOf(3, 3)
val thirdTarget = 6
println(hashTwoSum(thirdNumbers, thirdTarget).contentToString())
}
/**
- 方法一:枚举算法
- 时间复杂度:O(N^2),其中N是数组的元素数量
- 空间复杂度:O(1)
- @param numbers int类型的数组
- @param target 目标值
- @return 结果
*/
private fun twoSum(numbers: IntArray, target: Int): IntArray {
numbers.forEachIndexed { index, number ->
for (i in index + 1 until numbers.size) {
if (number + numbers[i] == target) {
return intArrayOf(index, i)
}
}
}
return intArrayOf()
}
/**
- 方法二:哈希表
- 时间复杂度:O(N),其中N是数组的元素数量
- 空间复杂度:O(N),其中N是数组的元素数量
- @param numbers int类型的数组
- @param target 目标值
- @return 结果
*/
private fun hashTwoSum(numbers: IntArray, target: Int): IntArray {
hashMapOf<Int, Int>().apply {
numbers.forEachIndexed { index, number ->
val other = target - number
if (containsKey(other)) {
val otherIndex = get(other) ?: 0
return intArrayOf(otherIndex, index)
}
put(number, index)
}
}
return intArrayOf()
}
}
题解
枚举
/**
- 方法一:枚举
- 时间复杂度:O(N^2),其中N是数组的元素数量
- 空间复杂度:O(1)
- @param numbers int类型的数组
- @param target 目标值
- @return 结果
*/
private fun twoSum(numbers: IntArray, target: Int): IntArray {
numbers.forEachIndexed { index, number ->
for (i in index + 1 until numbers.size) {
if (number + numbers[i] == target) {
return intArrayOf(index, i)
}
}
}
return intArrayOf()
}
时间复杂度:O(N^2),其中N是数组的元素数量。
空间复杂度:O(1)。
暴力解法,遍历numbers数组中每一个元素,并且找到任意两个相加等于target的元素,要注意的是,假设当前元素的索引是i,i前面的元素其实已经和i匹配过,我们就不需要再次匹配了。
哈希表
/**
- 方法二:哈希表
- 时间复杂度:O(N),其中N是数组的元素数量
- 空间复杂度:O(N),其中N是数组的元素数量
- @param numbers int类型的数组
- @param target 目标值
- @return 结果
*/
private fun hashTwoSum(numbers: IntArray, target: Int): IntArray {
hashMapOf<Int, Int>().apply {
numbers.forEachIndexed { index, number ->
val other = target - number
if (containsKey(other)) {
val otherIndex = get(other) ?: 0
return intArrayOf(otherIndex, index)
}
put(number, index)
}
}
return intArrayOf()
}
时间复杂度:O(N),其中N是数组的元素数量。
时间复杂度:O(N),其中N是数组的元素数量。
假设当前元素是number,方法一在查找另外一个元素的时候,也就是查找target - number的消耗太多时间了,我们可以使用HashMap存储,以元素作为key,以索引作为value,这样就可以通过containsKey方法快速地查找到这个元素对应的索引,至于为什么不用containsValue方法而使用containsKey方法,这是因为HashMap使用链地址法来解决哈希冲突的,简单来说,就是数组和链表的结合,每个数组元素都是一个链表结构,首先调用key的hashCode方法得到哈希值(该方法适用于每个Java对象),然后再通过哈希算法的后两步运算(高位运算、取模运算)来定位该键值对对应的存储位置,所以通过key去查找元素的效率会比通过value的效率高,Oracle官方说,用这两个方法遍历HashMap中的所有元素,containsValue方法所需的时间比containsKey方法多出几个数量级,所以我们使用containsKey方法来再提升效率。
要注意的是,我们不要使用mutableMapOf方法,而是使用hashMapOf方法,因为mutableMapOf方法使用的是LinkedHashMap,而hashMapOf方法使用的是HashMap,LinkedHashMap通过维护一个额外的双向链表来保证迭代顺序,但是在该题目其实是不需要的,不需要为此增加时间和空间上的开销,源码如下所示:
// Maps.kt
/**
- Returns an empty new [MutableMap].
- The returned map preserves the entry iteration order.
- @sample samples.collections.Maps.Instantiation.emptyMutableMap
*/
@SinceKotlin(“1.1”)
@kotlin.internal.InlineOnly
public inline fun <K, V> mutableMapOf(): MutableMap<K, V> = LinkedHashMap()
两数相加(Add Two Numbers)
难度:中等
代码
Java
/**
- Created by TanJiaJun on 2021/6/5.
-
- 两数相加(Add Two Numbers)
- 难度:中等
- @see Add Two Numbers
*/
class AddTwoNumbers {
public static void main(String[] args) {
// 示例一
System.out.print(“示例一:”);
Node firstNode =
new Node(2,
new Node(4,
new Node(3)));
Node secondNode =
new Node(5,
new Node(6,
new Node(4)));
printNode(addTwoNumbers(firstNode, secondNode));
System.out.print(“\n”);
// 示例二
System.out.print(“示例二:”);
Node thirdNode = new Node(0);
Node fourthNode = new Node(0);
printNode(addTwoNumbers(thirdNode, fourthNode));
System.out.print(“\n”);
// 示例三
System.out.print(“示例三:”);
Node fifthNode =
new Node(9,
new Node(9,
new Node(9,
new Node(9,
new Node(9,
new Node(9,
new Node(9)))))));
Node sixthNode =
new Node(9,
new Node(9,
new Node(9,
new Node(9))));
printNode(addTwoNumbers(fifthNode, sixthNode));
System.out.print(“\n”);
// 示例四
System.out.print(“示例四:”);
Node seventhNode = new Node(2);
Node eightNode = new Node(8);
printNode(addTwoNumbers(seventhNode, eightNode));
}
/**
- 时间复杂度:O(Max(m, n)),其中m是第一个结点的长度,n是第二个结点的长度
- 空间复杂度:O(1)
- @param firstNode 第一个结点
- @param secondNode 第二个结点
- @return 结果
*/
private static Node addTwoNumbers(Node firstNode, Node secondNode) {
Node dummyNode = new Node(-1);
Node node = dummyNode;
int carry = 0;
// 遍历两个链表
while (firstNode != null || secondNode != null) {
// 如果两个链表的长度不相同的话,就在短的链表的后面通过添加若干个0
int firstValue = firstNode != null ? firstNode.item : 0;
int secondValue = secondNode != null ? secondNode.item : 0;
int value = firstValue + secondValue + carry;
int newItem = value % 10;
// 相加后的进位值通过carry来存储
carry = value / 10;
Node newNode = new Node(newItem);
if (firstNode != null) {
firstNode = firstNode.next;
}
if (secondNode != null) {
secondNode = secondNode.next;
}
// 如果在遍历完这两个链表后,carry的值大于0,那么就应该在链表的后面增加一个新的结点来存储这个值
if (firstNode == null && secondNode == null && carry > 0) {
newNode.next = new Node(carry);
}
node.next = newNode;
node = node.next;
}
return dummyNode.next;
}
/**
- 打印结点
- @param node 结点
*/
private static void printNode(Node node) {
System.out.print(“[”);
while (node != null) {
System.out.print(node.item);
node = node.next;
if (node != null) {
System.out.print(“,”);
}
}
System.out.print(“]”);
}
private static class Node {
int item;
Node next;
Node(int item) {
this.item = item;
}
Node(int item, Node next) {
this.item = item;
this.next = next;
}
}
}
Kotlin
/**
- Created by TanJiaJun on 2021/6/6.
-
- 两数相加(Add Two Numbers)
- 难度:中等
- @see Add Two Numbers
*/
object AddTwoNumbersKotlin {
@JvmStatic
fun main(args: Array) {
// 示例一
print(“示例一:”)
val firstNode =
Node(
2,
Node(
4,
Node(3)
)
)
val secondNode =
Node(
5,
Node(
6,
Node(4)
)
)
printNode(addTwoNumbers(firstNode, secondNode))
print(“\n”)
// 示例二
print(“示例二:”)
val thirdNode = Node(0)
val fourthNode = Node(0)
printNode(addTwoNumbers(thirdNode, fourthNode))
print(“\n”)
// 示例三
print(“示例三:”)
val fifthNode =
Node(
9,
Node(
9,
Node(
9,
Node(
9,
Node(
9,
Node(
9,
Node(9)
)
)
)
)
)
)
val sixthNode =
Node(
9,
Node(
9,
Node(
9,
Node(9)
)
)
)
printNode(addTwoNumbers(fifthNode, sixthNode))
print(“\n”)
// 示例四
print(“示例四:”)
val seventhNode = Node(2)
val eightNode = Node(8)
printNode(addTwoNumbers(seventhNode, eightNode))
}
/**
- 时间复杂度:O(Max(m, n)),其中m是第一个结点的长度,n是第二个结点的长度
- 空间复杂度:O(1)
- @param first 第一个结点
- @param second 第二个结点
- @return 结果
*/
private fun addTwoNumbers(first: Node?, second: Node?): Node {
var firstNode = first
var secondNode = second
val dummy = Node(-1)
var node: Node = dummy
var carry = 0
// 遍历两个链表
while (firstNode != null || secondNode != null) {
// 如果两个链表的长度不相同的话,就在短的链表的后面通过添加若干个0
val firstValue = firstNode?.item ?: 0
val secondValue = secondNode?.item ?: 0
val value = firstValue + secondValue + carry
val newItem = value.rem(10)
// 相加后的进位值通过carry来存储
carry = value.div(10)
val newNode = Node(newItem)
firstNode?.let { firstNode = it.next }
secondNode?.let { secondNode = it.next }
// 如果在遍历完这两个链表后,carry的值大于0,那么就应该在链表的后面增加一个新的结点来存储这个值
if (firstNode == null && secondNode == null && carry > 0) {
newNode.next = Node(carry)
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
建议
当我们出去找工作,或者准备找工作的时候,我们一定要想,我面试的目标是什么,我自己的技术栈有哪些,近期能掌握的有哪些,我的哪些短板 ,列出来,有计划的去完成,别看前两天掘金一些大佬在驳来驳去 ,他们的观点是他们的,不要因为他们的观点,膨胀了自己,影响自己的学习节奏。基础很大程度决定你自己技术层次的厚度,你再熟练框架也好,也会比你便宜的,性价比高的替代,很现实的问题但也要有危机意识,当我们年级大了,有哪些亮点,与比我们经历更旺盛的年轻小工程师,竞争。
-
无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!!!!!!!
-
准备想说怎么样写简历,想象算了,我觉得,技术就是你最好的简历
-
我希望每一个努力生活的it工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
-
有什么问题想交流,欢迎给我私信,欢迎评论
【附】相关架构及资料
内含往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术
度决定你自己技术层次的厚度,你再熟练框架也好,也会比你便宜的,性价比高的替代,很现实的问题但也要有危机意识,当我们年级大了,有哪些亮点,与比我们经历更旺盛的年轻小工程师,竞争。
-
无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!!!!!!!
-
准备想说怎么样写简历,想象算了,我觉得,技术就是你最好的简历
-
我希望每一个努力生活的it工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
-
有什么问题想交流,欢迎给我私信,欢迎评论
【附】相关架构及资料
[外链图片转存中…(img-G01ELh8a-1712037965372)]
[外链图片转存中…(img-5kyooYt3-1712037965373)]
内含往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术