498.Diagonal Traverse-M

本文介绍了一种算法,该算法能够按照S型对角线方式遍历给定的M*N矩阵,并返回按此顺序排列的一维数组。通过控制坐标变化的方向,实现了对角线遍历的效果。

题目内容

给定一个M*N大小的矩阵(M行,N列),要求返还一个以下图所示方法得到的一个一维数组

这里写图片描述

这道题从题意来看,很简洁,要求按照对角线以S型遍历整个数组,从而得到一个特定顺序的一维数组,如果是人来完成这个任务,恐怕五六岁的小朋友都能轻易做到。但要怎么让电脑完成这件事呢?你需要注意到这几个点:
  • 上图中任一对角线上的所有点, 横纵坐标之和是相同的。
  • 横纵坐标之和的奇偶性决定了对角线应该向右上方遍历,还是应该向左下方遍历,即横纵坐标的变化方法;当和为奇数时,向左下方遍历;和为偶数时,向右上方遍历。
  • 重要的一点,每次对角线遍历到达上下左右四个边界后,应该怎么选择起点以及更新横纵坐标变换的方式,下面我将结合代码对这一点加以阐述。
class Solution {
public:
    vector<int> findDiagonalOrder(vector<vector<int>>& matrix) {
        vector<int> result;
        if (matrix.size() == 0) return result;
        // 初始遍历方向为右上方
        int row = matrix.size(), col = matrix[0].size();
        int sum = 0, x = 0, y = 0, dx = -1, dy = 1;

        while (x < row && y < col) {
            result.push_back(matrix[x][y]);
            x += dx;
            y += dy;
            if (x < 0 || y < 0 || x >= row || y >= col) {
                ++sum;
                if (x < 0) {
                    if (y < 0) {
                        // 这种情况不会发生
                    } else if (y >= col) {
                        // 遍历接触到右侧边界线
                        x += 2;
                        y = col - 1;
                    } else {
                        // 遍历接触到上侧边界线
                        x = 0;
                    }
                } else if (x >= row) {
                    x = r - 1;
                    if (y < 0) {
                        // 遍历接触到下侧边界线
                        y += 2;
                    } else if (y >= col) {
                        // this condition won't happen
                    } else {
                        // 遍历接触到下侧边界线
                        y += 2;
                    }
                } else {
                    if (y < 0) {
                        // 遍历接触到左侧边界线
                        y = 0;
                    } else if (y >= col) {
                        // 遍历接触到右侧边界线
                        y = col - 1;
                        x += 2;
                    }
                }

                // 根据奇偶性确定下一次的遍历方向
                if (sum % 2 == 1) {
                    dx = 1;
                    dy = -1;
                } else {
                    dx = -1;
                    dy = 1;
                }

            }
        }
        return result;
    }
};
android.animation.AnimationHandler instance 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Leaking: UNKNOWN 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Retaining 11.7 MB in 233011 objects 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ↓ AnimationHandler.mAnimationCallbacks 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ~~~~~~~~~~~~~~~~~~~ 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D ├─ java.util.ArrayList instance 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Leaking: UNKNOWN 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Retaining 11.7 MB in 233002 objects 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ↓ ArrayList[0] 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ~~~ 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D ├─ android.animation.ValueAnimator instance 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Leaking: UNKNOWN 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Retaining 11.7 MB in 232952 objects 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ mListeners = null 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ↓ ValueAnimator.mUpdateListeners 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ~~~~~~~~~~~~~~~~ 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D ├─ java.util.ArrayList instance 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Leaking: UNKNOWN 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Retaining 11.7 MB in 232939 objects 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ↓ ArrayList[0] 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ~~~ 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D ├─ com.xxlink.libskeleton.AnimationManager$$ExternalSyntheticLambda1 instance 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Leaking: UNKNOWN 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Retaining 11.7 MB in 232937 objects 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ↓ AnimationManager$$ExternalSyntheticLambda1.f$0 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ~~~ 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D ├─ com.xxlink.libskeleton.AnimationManager instance 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Leaking: UNKNOWN 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Retaining 11.7 MB in 232936 objects 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ↓ AnimationManager.targetView 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ~~~~~~~~~~ 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D ├─ com.xxlink.design.card.xxConstraintCardView instance 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Leaking: YES (View.mContext references a destroyed activity) 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Retaining 11.7 MB in 232923 objects 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ View not part of a window view hierarchy 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ View.mAttachInfo is null (view detached) 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ View.mWindowAttachCount = 1 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ mContext instance of androidx.appcompat.view.ContextThemeWrapper, wrapping activity xx.sdncontroller. 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ui.monitor.SdnControllerMonitorActivity with mDestroyed = true 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ ↓ View.mContext 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D ├─ androidx.appcompat.view.ContextThemeWrapper instance 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ Leaking: YES (xxConstraintCardView↑ is leaking and ContextThemeWrapper wraps an Activity with Activity.mDestroyed 2025-12-14 16:18:28.927 9328-9578 LeakCanary xx D │ true)class AnimationManager( private val targetView: View, private val config: SkeletonConfig = SkeletonDefaults.defaultConfig ) { private var animator: ValueAnimator? = null private val appliedDrawables = mutableListOf<Pair<View, Drawable>>() private val paint = Paint(Paint.ANTI_ALIAS_FLAG) // 用于监听布局变化,以便在宽高改变时重启动画 private var currentLayoutChangeListener: View.OnLayoutChangeListener? = null /** * 启动 Shimmer 动画 * 若已有动画运行,则先停止再启动新动画 */ fun start() { stop() // 清理旧状态 if (targetView.width == 0 || targetView.height == 0) { targetView.post { setupAndStart() } } else { setupAndStart() } } private fun setupAndStart() { // 创建并添加布局变化监听器 val layoutChangeListener = View.OnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> val newWidth = right - left val newHeight = bottom - top val oldWidth = oldRight - oldLeft val oldHeight = oldBottom - oldTop if (newWidth != oldWidth || newHeight != oldHeight) { restart() } } targetView.addOnLayoutChangeListener(layoutChangeListener) currentLayoutChangeListener = layoutChangeListener // 立即启动一次 restart() } /** * 停止当前动画并清理所有资源 */ fun stop() { stopAnimator() currentLayoutChangeListener?.let { targetView.removeOnLayoutChangeListener(it) } currentLayoutChangeListener = null appliedDrawables.forEach { (view, drawable) -> try { view.overlay.remove(drawable) } catch (e: Exception) { Log.e("AnimationManager", "Failed to remove overlay from view", e) } } appliedDrawables.clear() } /** * 停止当前动画并立即以最新尺寸重新启动 */ private fun restart() { stopAnimator() startAnimator() } /** * 创建并启动新的 shimmer 动画 */ private fun startAnimator() { val animator = ValueAnimator.ofFloat(-1f, 2f).apply { duration = config.shimmerDuration repeatCount = ValueAnimator.INFINITE interpolator = AccelerateDecelerateInterpolator() addUpdateListener { animation -> val fraction = animation.animatedValue as Float updateShader(fraction) invalidateAllDrawables() } } this.animator = animator animator.start() // 应用统一 shimmer drawable applyShimmerToTargetView() } /** * 停止动画但不清除监听器 */ private fun stopAnimator() { animator?.cancel() animator = null } /** * 更新渐变着色器(Shader),实现移动的高亮光效 */ private fun updateShader(offsetFraction: Float) { val width = targetView.width.toFloat() val height = targetView.height.toFloat() if (width <= 0f || height <= 0f) return val bandWidth = width * config.shimmerWidth val angleRad = Math.toRadians(config.shimmerAngle.toDouble()) val cos = Math.cos(angleRad).toFloat() val sin = Math.sin(angleRad).toFloat() val diagonal = Math.sqrt((width * width + height * height).toDouble()).toFloat() val offset = offsetFraction * diagonal val startX = -diagonal * cos + offset val startY = -diagonal * sin + offset val endX = startX + bandWidth * cos val endY = startY + bandWidth * sin val colors = intArrayOf( Color.TRANSPARENT, ContextCompat.getColor(targetView.context, config.shimmerColorRes), Color.TRANSPARENT ) val positions = floatArrayOf(0f, 0.5f, 1f) val shader = LinearGradient(startX, startY, endX, endY, colors, positions, Shader.TileMode.CLAMP) paint.shader = shader } /** * 触发所有 shimmer drawable 重绘,显示最新 shader 效果 */ private fun invalidateAllDrawables() { appliedDrawables.forEach { (_, drawable) -> drawable.invalidateSelf() } } /** * 只创建一个 drawable,挂在最外层 targetView * 并在 draw 时递归裁剪子 view 的可见区域 */ private fun applyShimmerToTargetView() { appliedDrawables.forEach { (view, drawable) -> view.overlay.remove(drawable) } appliedDrawables.clear() val unifiedDrawable = object : Drawable() { private val rectF = RectF() private val clipPath = Path() override fun draw(canvas: Canvas) { if (paint.shader == null) return clipPath.reset() collectVisibleChildPaths(targetView, clipPath) canvas.save() canvas.clipPath(clipPath) rectF.set(bounds) canvas.drawRect(rectF, paint) canvas.restore() } private fun collectVisibleChildPaths(view: View, path: Path) { if (view.visibility != View.VISIBLE || view.tag == "ignore_ani") return if (view is ViewGroup) { for (i in 0 until view.childCount) { collectVisibleChildPaths(view.getChildAt(i), path) } } else { val location = IntArray(2) view.getLocationOnScreen(location) val rootLocation = IntArray(2) targetView.getLocationOnScreen(rootLocation) val left = (location[0] - rootLocation[0]).toFloat() val top = (location[1] - rootLocation[1]).toFloat() val right = left + view.width val bottom = top + view.height // 尝试从背景中提取圆角信息 val cornerRadius = ViewUtils.dp2px(targetView.context,config.cornerRadius).toFloat() if (cornerRadius > 0f) { path.addRoundRect( left, top, right, bottom, cornerRadius, cornerRadius, Path.Direction.CW ) } else { path.addRect(left, top, right, bottom, Path.Direction.CW) } } } override fun setAlpha(alpha: Int) {} override fun setColorFilter(colorFilter: ColorFilter?) {} override fun getOpacity(): Int = PixelFormat.TRANSLUCENT }.apply { setBounds(0, 0, targetView.width, targetView.height) } targetView.overlay.add(unifiedDrawable) appliedDrawables.add(targetView to unifiedDrawable) } }fun ViewGroup.showSkeleton(config: SkeletonConfig = SkeletonDefaults.defaultConfig) { hideSkeleton() val resId = config.customLayoutRes // 如果 resId 为空,直接返回 resId ?: return val skeletonView = LayoutInflater.from(context).inflate(resId, this, false) // 根据容器类型生成适配的布局参数 val layoutParams = generateOverlayLayoutParams() // 防止点击/触摸事件穿透 setupTouchInterceptor(skeletonView) val existingListener = getTag(R.id.tag_attach_listener) as? View.OnAttachStateChangeListener if (existingListener == null) { val attachListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) {} override fun onViewDetachedFromWindow(v: View) { hideSkeleton() v.removeOnAttachStateChangeListener(this) v.setTag(R.id.tag_attach_listener, null) } } this.addOnAttachStateChangeListener(attachListener) this.setTag(R.id.tag_attach_listener, attachListener) } addView(skeletonView, layoutParams) val animation = AnimationManager(skeletonView as ViewGroup, config) animation.start() setTag(R.id.tag_skeleton_view, SkeletonView(skeletonView, animation)) } private fun ViewGroup.generateOverlayLayoutParams(): ViewGroup.LayoutParams { return when (this) { is FrameLayout -> FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ).apply { gravity = Gravity.START or Gravity.TOP } is LinearLayout -> LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) is RelativeLayout -> RelativeLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ).apply { addRule(RelativeLayout.ALIGN_PARENT_START) addRule(RelativeLayout.ALIGN_PARENT_TOP) addRule(RelativeLayout.ALIGN_PARENT_END) addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) } is ConstraintLayout -> ConstraintLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, // MATCH_CONSTRAINT 0 // MATCH_CONSTRAINT ).apply { topToTop = ConstraintLayout.LayoutParams.PARENT_ID bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID startToStart = ConstraintLayout.LayoutParams.PARENT_ID endToEnd = ConstraintLayout.LayoutParams.PARENT_ID } else -> ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) } } private fun setupTouchInterceptor(view: View) { view.setOnTouchListener { _, _ -> true } } /** * 隐藏 ViewGroup 的骨架屏,恢复原始子 View 状态 */ fun ViewGroup.hideSkeleton() { // 通过 tag 找到骨架 View,停止动画并移除 val skeletonTag = getTag(R.id.tag_skeleton_view) as? SkeletonView ?: return val skeletonView = skeletonTag.view val animationManager = skeletonTag.animationManager // 停止所有动画 animationManager.stop() // 从父布局中移除骨架 View if (skeletonView.parent == this) { removeView(skeletonView) } // 清理 Tag,避免重复添加或内存泄漏 setTag(R.id.tag_skeleton_view, null) } fun ViewGroup.skeleton(): SkeletonBuilder { return SkeletonBuilder(this) }这个为什么会泄露呢,明明如果detach了就会执行hide销毁动画啊
最新发布
12-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值